diff options
Diffstat (limited to 'xbmc/interfaces/json-rpc')
52 files changed, 19371 insertions, 0 deletions
diff --git a/xbmc/interfaces/json-rpc/AddonsOperations.cpp b/xbmc/interfaces/json-rpc/AddonsOperations.cpp new file mode 100644 index 0000000..db6c90a --- /dev/null +++ b/xbmc/interfaces/json-rpc/AddonsOperations.cpp @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "AddonsOperations.h" + +#include "JSONUtils.h" +#include "ServiceBroker.h" +#include "TextureCache.h" +#include "addons/AddonDatabase.h" +#include "addons/AddonManager.h" +#include "addons/PluginSource.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "messaging/ApplicationMessenger.h" +#include "utils/FileUtils.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +using namespace JSONRPC; +using namespace ADDON; + +JSONRPC_STATUS CAddonsOperations::GetAddons(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::vector<AddonType> addonTypes; + AddonType addonType = CAddonInfo::TranslateType(parameterObject["type"].asString()); + CPluginSource::Content content = CPluginSource::Translate(parameterObject["content"].asString()); + CVariant enabled = parameterObject["enabled"]; + CVariant installed = parameterObject["installed"]; + + // ignore the "content" parameter if the type is specified but not a plugin or script + if (addonType != AddonType::UNKNOWN && addonType != AddonType::PLUGIN && + addonType != AddonType::SCRIPT) + content = CPluginSource::UNKNOWN; + + if (addonType >= AddonType::VIDEO && addonType <= AddonType::EXECUTABLE) + { + addonTypes.push_back(AddonType::PLUGIN); + addonTypes.push_back(AddonType::SCRIPT); + + switch (addonType) + { + case AddonType::VIDEO: + content = CPluginSource::VIDEO; + break; + case AddonType::AUDIO: + content = CPluginSource::AUDIO; + break; + case AddonType::IMAGE: + content = CPluginSource::IMAGE; + break; + case AddonType::GAME: + content = CPluginSource::GAME; + break; + case AddonType::EXECUTABLE: + content = CPluginSource::EXECUTABLE; + break; + default: + break; + } + } + else + addonTypes.push_back(addonType); + + VECADDONS addons; + for (const auto& typeIt : addonTypes) + { + VECADDONS typeAddons; + if (typeIt == AddonType::UNKNOWN) + { + if (!enabled.isBoolean()) //All + { + if (!installed.isBoolean() || installed.asBoolean()) + CServiceBroker::GetAddonMgr().GetInstalledAddons(typeAddons); + if (!installed.isBoolean() || (installed.isBoolean() && !installed.asBoolean())) + CServiceBroker::GetAddonMgr().GetInstallableAddons(typeAddons); + } + else if (enabled.asBoolean() && (!installed.isBoolean() || installed.asBoolean())) //Enabled + CServiceBroker::GetAddonMgr().GetAddons(typeAddons); + else if (!installed.isBoolean() || installed.asBoolean()) + CServiceBroker::GetAddonMgr().GetDisabledAddons(typeAddons); + } + else + { + if (!enabled.isBoolean()) //All + { + if (!installed.isBoolean() || installed.asBoolean()) + CServiceBroker::GetAddonMgr().GetInstalledAddons(typeAddons, typeIt); + if (!installed.isBoolean() || (installed.isBoolean() && !installed.asBoolean())) + CServiceBroker::GetAddonMgr().GetInstallableAddons(typeAddons, typeIt); + } + else if (enabled.asBoolean() && (!installed.isBoolean() || installed.asBoolean())) //Enabled + CServiceBroker::GetAddonMgr().GetAddons(typeAddons, typeIt); + else if (!installed.isBoolean() || installed.asBoolean()) + CServiceBroker::GetAddonMgr().GetDisabledAddons(typeAddons, typeIt); + } + + addons.insert(addons.end(), typeAddons.begin(), typeAddons.end()); + } + + // remove library addons + for (int index = 0; index < (int)addons.size(); index++) + { + std::shared_ptr<CPluginSource> plugin; + if (content != CPluginSource::UNKNOWN) + plugin = std::dynamic_pointer_cast<CPluginSource>(addons.at(index)); + + if ((addons.at(index)->Type() <= AddonType::UNKNOWN || + addons.at(index)->Type() >= AddonType::MAX_TYPES) || + ((content != CPluginSource::UNKNOWN && plugin == NULL) || + (plugin != NULL && !plugin->Provides(content)))) + { + addons.erase(addons.begin() + index); + index--; + } + } + + int start, end; + HandleLimits(parameterObject, result, addons.size(), start, end); + + CAddonDatabase addondb; + for (int index = start; index < end; index++) + FillDetails(addons.at(index), parameterObject["properties"], result["addons"], addondb, true); + + return OK; +} + +JSONRPC_STATUS CAddonsOperations::GetAddonDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string id = parameterObject["addonid"].asString(); + AddonPtr addon; + if (!CServiceBroker::GetAddonMgr().GetAddon(id, addon, OnlyEnabled::CHOICE_NO) || + addon.get() == NULL || addon->Type() <= AddonType::UNKNOWN || + addon->Type() >= AddonType::MAX_TYPES) + return InvalidParams; + + CAddonDatabase addondb; + FillDetails(addon, parameterObject["properties"], result["addon"], addondb); + + return OK; +} + +JSONRPC_STATUS CAddonsOperations::SetAddonEnabled(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string id = parameterObject["addonid"].asString(); + AddonPtr addon; + if (!CServiceBroker::GetAddonMgr().GetAddon(id, addon, OnlyEnabled::CHOICE_NO) || + addon == nullptr || addon->Type() <= AddonType::UNKNOWN || + addon->Type() >= AddonType::MAX_TYPES) + return InvalidParams; + + bool disabled = false; + if (parameterObject["enabled"].isBoolean()) + { + disabled = !parameterObject["enabled"].asBoolean(); + } + // we need to toggle the current disabled state of the addon + else if (parameterObject["enabled"].isString()) + { + disabled = !CServiceBroker::GetAddonMgr().IsAddonDisabled(id); + } + else + { + return InvalidParams; + } + + bool success = disabled + ? CServiceBroker::GetAddonMgr().DisableAddon(id, AddonDisabledReason::USER) + : CServiceBroker::GetAddonMgr().EnableAddon(id); + + return success ? ACK : InvalidParams; +} + +JSONRPC_STATUS CAddonsOperations::ExecuteAddon(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string id = parameterObject["addonid"].asString(); + AddonPtr addon; + if (!CServiceBroker::GetAddonMgr().GetAddon(id, addon, OnlyEnabled::CHOICE_YES) || + addon.get() == NULL || addon->Type() < AddonType::VISUALIZATION || + addon->Type() >= AddonType::MAX_TYPES) + return InvalidParams; + + std::string argv; + CVariant params = parameterObject["params"]; + if (params.isObject()) + { + for (CVariant::const_iterator_map it = params.begin_map(); it != params.end_map(); ++it) + { + if (it != params.begin_map()) + argv += ","; + argv += it->first + "=" + it->second.asString(); + } + } + else if (params.isArray()) + { + for (CVariant::const_iterator_array it = params.begin_array(); it != params.end_array(); ++it) + { + if (it != params.begin_array()) + argv += ","; + argv += StringUtils::Paramify(it->asString()); + } + } + else if (params.isString()) + { + if (!params.empty()) + argv = StringUtils::Paramify(params.asString()); + } + + std::string cmd; + if (params.empty()) + cmd = StringUtils::Format("RunAddon({})", id); + else + cmd = StringUtils::Format("RunAddon({}, {})", id, argv); + + if (params["wait"].asBoolean()) + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd); + else + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd); + + return ACK; +} + +static CVariant Serialize(const AddonPtr& addon) +{ + CVariant variant; + variant["addonid"] = addon->ID(); + variant["type"] = CAddonInfo::TranslateType(addon->Type(), false); + variant["name"] = addon->Name(); + variant["version"] = addon->Version().asString(); + variant["summary"] = addon->Summary(); + variant["description"] = addon->Description(); + variant["path"] = addon->Path(); + variant["author"] = addon->Author(); + variant["thumbnail"] = addon->Icon(); + variant["disclaimer"] = addon->Disclaimer(); + variant["fanart"] = addon->FanArt(); + + variant["dependencies"] = CVariant(CVariant::VariantTypeArray); + for (const auto& dep : addon->GetDependencies()) + { + CVariant info(CVariant::VariantTypeObject); + info["addonid"] = dep.id; + info["minversion"] = dep.versionMin.asString(); + info["version"] = dep.version.asString(); + info["optional"] = dep.optional; + variant["dependencies"].push_back(std::move(info)); + } + if (addon->LifecycleState() == AddonLifecycleState::BROKEN) + variant["broken"] = addon->LifecycleStateDescription(); + else + variant["broken"] = false; + if (addon->LifecycleState() == AddonLifecycleState::DEPRECATED) + variant["deprecated"] = addon->LifecycleStateDescription(); + else + variant["deprecated"] = false; + variant["extrainfo"] = CVariant(CVariant::VariantTypeArray); + for (const auto& kv : addon->ExtraInfo()) + { + CVariant info(CVariant::VariantTypeObject); + info["key"] = kv.first; + info["value"] = kv.second; + variant["extrainfo"].push_back(std::move(info)); + } + variant["rating"] = -1; + return variant; +} + +void CAddonsOperations::FillDetails(const std::shared_ptr<ADDON::IAddon>& addon, + const CVariant& fields, + CVariant& result, + CAddonDatabase& addondb, + bool append /* = false */) +{ + if (addon.get() == NULL) + return; + + CVariant addonInfo = Serialize(addon); + + CVariant object; + object["addonid"] = addonInfo["addonid"]; + object["type"] = addonInfo["type"]; + + for (unsigned int index = 0; index < fields.size(); index++) + { + std::string field = fields[index].asString(); + + // we need to manually retrieve the enabled / installed state of every addon + // from the addon database because it can't be read from addon.xml + if (field == "enabled") + { + object[field] = !CServiceBroker::GetAddonMgr().IsAddonDisabled(addon->ID()); + } + else if (field == "installed") + { + object[field] = CServiceBroker::GetAddonMgr().IsAddonInstalled(addon->ID()); + } + else if (field == "fanart" || field == "thumbnail") + { + std::string url = addonInfo[field].asString(); + // We need to check the existence of fanart and thumbnails as the addon simply + // holds where the art will be, not whether it exists. + bool needsRecaching; + std::string image = CServiceBroker::GetTextureCache()->CheckCachedImage(url, needsRecaching); + if (!image.empty() || CFileUtils::Exists(url)) + object[field] = CTextureUtils::GetWrappedImageURL(url); + else + object[field] = ""; + } + else if (addonInfo.isMember(field)) + object[field] = addonInfo[field]; + } + + if (append) + result.append(object); + else + result = object; +} diff --git a/xbmc/interfaces/json-rpc/AddonsOperations.h b/xbmc/interfaces/json-rpc/AddonsOperations.h new file mode 100644 index 0000000..727f418 --- /dev/null +++ b/xbmc/interfaces/json-rpc/AddonsOperations.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPC.h" + +#include <memory> + +namespace ADDON +{ +class CAddonDatabase; +class IAddon; +} + +class CVariant; + +namespace JSONRPC +{ + class CAddonsOperations : public CJSONUtils + { + public: + static JSONRPC_STATUS GetAddons(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetAddonDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS SetAddonEnabled(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS ExecuteAddon(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + private: + static void FillDetails(const std::shared_ptr<ADDON::IAddon>& addon, + const CVariant& fields, + CVariant& result, + ADDON::CAddonDatabase& addondb, + bool append = false); + }; +} diff --git a/xbmc/interfaces/json-rpc/ApplicationOperations.cpp b/xbmc/interfaces/json-rpc/ApplicationOperations.cpp new file mode 100644 index 0000000..aab680a --- /dev/null +++ b/xbmc/interfaces/json-rpc/ApplicationOperations.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ApplicationOperations.h" + +#include "CompileInfo.h" +#include "InputOperations.h" +#include "LangInfo.h" +#include "ServiceBroker.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationVolumeHandling.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "messaging/ApplicationMessenger.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +#include <cmath> +#include <string.h> + +using namespace JSONRPC; + +JSONRPC_STATUS CApplicationOperations::GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVariant properties = CVariant(CVariant::VariantTypeObject); + for (unsigned int index = 0; index < parameterObject["properties"].size(); index++) + { + std::string propertyName = parameterObject["properties"][index].asString(); + CVariant property; + JSONRPC_STATUS ret; + if ((ret = GetPropertyValue(propertyName, property)) != OK) + return ret; + + properties[propertyName] = property; + } + + result = properties; + + return OK; +} + +JSONRPC_STATUS CApplicationOperations::SetVolume(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + bool up = false; + if (parameterObject["volume"].isInteger()) + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appVolume = components.GetComponent<CApplicationVolumeHandling>(); + int oldVolume = static_cast<int>(appVolume->GetVolumePercent()); + int volume = static_cast<int>(parameterObject["volume"].asInteger()); + + appVolume->SetVolume(static_cast<float>(volume), true); + + up = oldVolume < volume; + } + else if (parameterObject["volume"].isString()) + { + JSONRPC_STATUS ret; + std::string direction = parameterObject["volume"].asString(); + if (direction.compare("increment") == 0) + { + ret = CInputOperations::SendAction(ACTION_VOLUME_UP, false, true); + up = true; + } + else if (direction.compare("decrement") == 0) + { + ret = CInputOperations::SendAction(ACTION_VOLUME_DOWN, false, true); + up = false; + } + else + return InvalidParams; + + if (ret != ACK && ret != OK) + return ret; + } + else + return InvalidParams; + + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_VOLUME_SHOW, + up ? ACTION_VOLUME_UP : ACTION_VOLUME_DOWN); + + return GetPropertyValue("volume", result); +} + +JSONRPC_STATUS CApplicationOperations::SetMute(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appVolume = components.GetComponent<CApplicationVolumeHandling>(); + if ((parameterObject["mute"].isString() && + parameterObject["mute"].asString().compare("toggle") == 0) || + (parameterObject["mute"].isBoolean() && + parameterObject["mute"].asBoolean() != appVolume->IsMuted())) + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast<void*>(new CAction(ACTION_MUTE))); + else if (!parameterObject["mute"].isBoolean() && !parameterObject["mute"].isString()) + return InvalidParams; + + return GetPropertyValue("muted", result); +} + +JSONRPC_STATUS CApplicationOperations::Quit(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT); + return ACK; +} + +JSONRPC_STATUS CApplicationOperations::GetPropertyValue(const std::string &property, CVariant &result) +{ + if (property == "volume" || property == "muted") + { + const auto& components = CServiceBroker::GetAppComponents(); + const auto appVolume = components.GetComponent<CApplicationVolumeHandling>(); + if (property == "volume") + result = static_cast<int>(std::lroundf(appVolume->GetVolumePercent())); + else if (property == "muted") + result = appVolume->IsMuted(); + } + else if (property == "name") + result = CCompileInfo::GetAppName(); + else if (property == "version") + { + result = CVariant(CVariant::VariantTypeObject); + result["major"] = CCompileInfo::GetMajor(); + result["minor"] = CCompileInfo::GetMinor(); + result["revision"] = CCompileInfo::GetSCMID(); + std::string tag = CCompileInfo::GetSuffix(); + if (StringUtils::StartsWithNoCase(tag, "alpha")) + { + result["tag"] = "alpha"; + result["tagversion"] = StringUtils::Mid(tag, 5); + } + else if (StringUtils::StartsWithNoCase(tag, "beta")) + { + result["tag"] = "beta"; + result["tagversion"] = StringUtils::Mid(tag, 4); + } + else if (StringUtils::StartsWithNoCase(tag, "rc")) + { + result["tag"] = "releasecandidate"; + result["tagversion"] = StringUtils::Mid(tag, 2); + } + else if (tag.empty()) + result["tag"] = "stable"; + else + result["tag"] = "prealpha"; + } + else if (property == "sorttokens") + { + result = CVariant(CVariant::VariantTypeArray); // Ensure no tokens returns as [] + std::set<std::string> sortTokens = g_langInfo.GetSortTokens(); + for (const auto& token : sortTokens) + result.append(token); + } + else if (property == "language") + result = g_langInfo.GetLocale().ToShortString(); + else + return InvalidParams; + + return OK; +} diff --git a/xbmc/interfaces/json-rpc/ApplicationOperations.h b/xbmc/interfaces/json-rpc/ApplicationOperations.h new file mode 100644 index 0000000..464f824 --- /dev/null +++ b/xbmc/interfaces/json-rpc/ApplicationOperations.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "FileItemHandler.h" +#include "JSONRPC.h" + +class CVariant; + +namespace JSONRPC +{ + class CApplicationOperations : CFileItemHandler + { + public: + static JSONRPC_STATUS GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS SetVolume(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetMute(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS Quit(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + private: + static JSONRPC_STATUS GetPropertyValue(const std::string &property, CVariant &result); + }; +} diff --git a/xbmc/interfaces/json-rpc/AudioLibrary.cpp b/xbmc/interfaces/json-rpc/AudioLibrary.cpp new file mode 100644 index 0000000..3c73a84 --- /dev/null +++ b/xbmc/interfaces/json-rpc/AudioLibrary.cpp @@ -0,0 +1,1372 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "AudioLibrary.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "TextureDatabase.h" +#include "Util.h" +#include "filesystem/Directory.h" +#include "messaging/ApplicationMessenger.h" +#include "music/Album.h" +#include "music/Artist.h" +#include "music/MusicDatabase.h" +#include "music/MusicDbUrl.h" +#include "music/MusicThumbLoader.h" +#include "music/Song.h" +#include "music/tags/MusicInfoTag.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/SortUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" + +using namespace MUSIC_INFO; +using namespace JSONRPC; +using namespace XFILE; + +JSONRPC_STATUS CAudioLibrary::GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVariant properties = CVariant(CVariant::VariantTypeObject); + CMusicDatabase musicdatabase; + // Make db connection once if one or more properties needs db access + for (CVariant::const_iterator_array it = parameterObject["properties"].begin_array(); + it != parameterObject["properties"].end_array(); ++it) + { + std::string propertyName = it->asString(); + if (propertyName == "librarylastupdated" || propertyName == "librarylastcleaned" || + propertyName == "artistlinksupdated" || propertyName == "songslastadded" || + propertyName == "albumslastadded" || propertyName == "artistslastadded" || + propertyName == "songsmodified" || propertyName == "albumsmodified" || + propertyName == "artistsmodified") + { + if (!musicdatabase.Open()) + return InternalError; + else + break; + } + } + + for (CVariant::const_iterator_array it = parameterObject["properties"].begin_array(); + it != parameterObject["properties"].end_array(); ++it) + { + std::string propertyName = it->asString(); + CVariant property; + if (propertyName == "missingartistid") + property = (int)BLANKARTIST_ID; + else if (propertyName == "librarylastupdated") + property = musicdatabase.GetLibraryLastUpdated(); + else if (propertyName == "librarylastcleaned") + property = musicdatabase.GetLibraryLastCleaned(); + else if (propertyName == "artistlinksupdated") + property = musicdatabase.GetArtistLinksUpdated(); + else if (propertyName == "songslastadded") + property = musicdatabase.GetSongsLastAdded(); + else if (propertyName == "albumslastadded") + property = musicdatabase.GetAlbumsLastAdded(); + else if (propertyName == "artistslastadded") + property = musicdatabase.GetArtistsLastAdded(); + else if (propertyName == "genreslastadded") + property = musicdatabase.GetGenresLastAdded(); + else if (propertyName == "songsmodified") + property = musicdatabase.GetSongsLastModified(); + else if (propertyName == "albumsmodified") + property = musicdatabase.GetAlbumsLastModified(); + else if (propertyName == "artistsmodified") + property = musicdatabase.GetArtistsLastModified(); + + properties[propertyName] = property; + } + + result = properties; + return OK; +} + + +JSONRPC_STATUS CAudioLibrary::GetArtists(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CMusicDbUrl musicUrl; + if (!musicUrl.FromString("musicdb://artists/")) + return InternalError; + + bool allroles = false; + if (parameterObject["allroles"].isBoolean()) + allroles = parameterObject["allroles"].asBoolean(); + + const CVariant &filter = parameterObject["filter"]; + + if (allroles) + musicUrl.AddOption("roleid", -1000); //All roles, any negative parameter overrides implicit roleid=1 filter required for backward compatibility + else if (filter.isMember("roleid")) + musicUrl.AddOption("roleid", static_cast<int>(filter["roleid"].asInteger())); + else if (filter.isMember("role")) + musicUrl.AddOption("role", filter["role"].asString()); + // Only one of (song) genreid/genre, albumid/album or songid/song or rules type filter is allowed by filter syntax + if (filter.isMember("genreid")) //Deprecated. Use "songgenre" or "artistgenre" + musicUrl.AddOption("genreid", static_cast<int>(filter["genreid"].asInteger())); + else if (filter.isMember("genre")) + musicUrl.AddOption("genre", filter["genre"].asString()); + if (filter.isMember("songgenreid")) + musicUrl.AddOption("genreid", static_cast<int>(filter["songgenreid"].asInteger())); + else if (filter.isMember("songgenre")) + musicUrl.AddOption("genre", filter["songgenre"].asString()); + else if (filter.isMember("albumid")) + musicUrl.AddOption("albumid", static_cast<int>(filter["albumid"].asInteger())); + else if (filter.isMember("album")) + musicUrl.AddOption("album", filter["album"].asString()); + else if (filter.isMember("songid")) + musicUrl.AddOption("songid", static_cast<int>(filter["songid"].asInteger())); + else if (filter.isObject()) + { + std::string xsp; + if (!GetXspFiltering("artists", filter, xsp)) + return InvalidParams; + + musicUrl.AddOption("xsp", xsp); + } + + bool albumArtistsOnly = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_SHOWCOMPILATIONARTISTS); + if (parameterObject["albumartistsonly"].isBoolean()) + albumArtistsOnly = parameterObject["albumartistsonly"].asBoolean(); + musicUrl.AddOption("albumartistsonly", albumArtistsOnly); + + SortDescription sorting; + ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd); + if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes)) + return InvalidParams; + + int total; + std::set<std::string> fields; + if (parameterObject.isMember("properties") && parameterObject["properties"].isArray()) + { + for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array(); + field != parameterObject["properties"].end_array(); ++field) + fields.insert(field->asString()); + } + + musicdatabase.SetTranslateBlankArtist(false); + if (!musicdatabase.GetArtistsByWhereJSON(fields, musicUrl.ToString(), result, total, sorting)) + return InternalError; + + int start, end; + HandleLimits(parameterObject, result, total, start, end); + + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetArtistDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int artistID = (int)parameterObject["artistid"].asInteger(); + + CMusicDbUrl musicUrl; + if (!musicUrl.FromString("musicdb://artists/")) + return InternalError; + + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + musicUrl.AddOption("artistid", artistID); + + CFileItemList items; + CDatabase::Filter filter; + if (!musicdatabase.GetArtistsByWhere(musicUrl.ToString(), filter, items) || items.Size() != 1) + return InvalidParams; + + // Add "artist" to "properties" array by default + CVariant param = parameterObject; + if (!param.isMember("properties")) + param["properties"] = CVariant(CVariant::VariantTypeArray); + param["properties"].append("artist"); + + //Get roleids, roles etc. if needed + JSONRPC_STATUS ret = GetAdditionalArtistDetails(parameterObject, items, musicdatabase); + if (ret != OK) + return ret; + + HandleFileItem("artistid", false, "artistdetails", items[0], param, param["properties"], result, false); + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CMusicDbUrl musicUrl; + if (!musicUrl.FromString("musicdb://albums/")) + return InternalError; + + if (parameterObject["includesingles"].asBoolean()) + musicUrl.AddOption("show_singles", true); + + bool allroles = false; + if (parameterObject["allroles"].isBoolean()) + allroles = parameterObject["allroles"].asBoolean(); + + const CVariant &filter = parameterObject["filter"]; + + if (allroles) + musicUrl.AddOption("roleid", -1000); //All roles, override implicit roleid=1 filter required for backward compatibility + else if (filter.isMember("roleid")) + musicUrl.AddOption("roleid", static_cast<int>(filter["roleid"].asInteger())); + else if (filter.isMember("role")) + musicUrl.AddOption("role", filter["role"].asString()); + // Only one of genreid/genre, artistid/artist or rules type filter is allowed by filter syntax + if (filter.isMember("artistid")) + musicUrl.AddOption("artistid", static_cast<int>(filter["artistid"].asInteger())); + else if (filter.isMember("artist")) + musicUrl.AddOption("artist", filter["artist"].asString()); + else if (filter.isMember("genreid")) + musicUrl.AddOption("genreid", static_cast<int>(filter["genreid"].asInteger())); + else if (filter.isMember("genre")) + musicUrl.AddOption("genre", filter["genre"].asString()); + else if (filter.isObject()) + { + std::string xsp; + if (!GetXspFiltering("albums", filter, xsp)) + return InvalidParams; + + musicUrl.AddOption("xsp", xsp); + } + + SortDescription sorting; + ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd); + if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes)) + return InvalidParams; + + int total; + std::set<std::string> fields; + if (parameterObject.isMember("properties") && parameterObject["properties"].isArray()) + { + for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array(); + field != parameterObject["properties"].end_array(); ++field) + fields.insert(field->asString()); + } + + if (!musicdatabase.GetAlbumsByWhereJSON(fields, musicUrl.ToString(), result, total, sorting)) + return InternalError; + + if (!result.isNull()) + { + bool bFetchArt = fields.find("art") != fields.end(); + bool bFetchFanart = fields.find("fanart") != fields.end(); + if (bFetchArt || bFetchFanart) + { + CThumbLoader* thumbLoader = new CMusicThumbLoader(); + thumbLoader->OnLoaderStart(); + + std::set<std::string> artfields; + if (bFetchArt) + artfields.insert("art"); + if (bFetchFanart) + artfields.insert("fanart"); + + for (unsigned int index = 0; index < result["albums"].size(); index++) + { + CFileItem item; + item.GetMusicInfoTag()->SetDatabaseId(result["albums"][index]["albumid"].asInteger32(), MediaTypeAlbum); + + // Could use FillDetails, but it does unnecessary serialization of empty MusiInfoTag + // CFileItemPtr itemptr(new CFileItem(item)); + // FillDetails(item.GetMusicInfoTag(), itemptr, artfields, result["albums"][index], thumbLoader); + + thumbLoader->FillLibraryArt(item); + + if (bFetchFanart) + { + if (item.HasArt("fanart")) + result["albums"][index]["fanart"] = CTextureUtils::GetWrappedImageURL(item.GetArt("fanart")); + else + result["albums"][index]["fanart"] = ""; + } + if (bFetchArt) + { + CGUIListItem::ArtMap artMap = item.GetArt(); + CVariant artObj(CVariant::VariantTypeObject); + for (const auto& artIt : artMap) + { + if (!artIt.second.empty()) + artObj[artIt.first] = CTextureUtils::GetWrappedImageURL(artIt.second); + } + result["albums"][index]["art"] = artObj; + } + } + + delete thumbLoader; + } + } + + int start, end; + HandleLimits(parameterObject, result, total, start, end); + + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetAlbumDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int albumID = (int)parameterObject["albumid"].asInteger(); + + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CAlbum album; + if (!musicdatabase.GetAlbum(albumID, album, false)) + return InvalidParams; + + std::string path = StringUtils::Format("musicdb://albums/{}/", albumID); + + CFileItemPtr albumItem; + FillAlbumItem(album, path, albumItem); + + CFileItemList items; + items.Add(albumItem); + JSONRPC_STATUS ret = GetAdditionalAlbumDetails(parameterObject, items, musicdatabase); + if (ret != OK) + return ret; + + HandleFileItem("albumid", false, "albumdetails", items[0], parameterObject, parameterObject["properties"], result, false); + + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CMusicDbUrl musicUrl; + if (!musicUrl.FromString("musicdb://songs/")) + return InternalError; + + if (parameterObject["singlesonly"].asBoolean()) + musicUrl.AddOption("singles", true); + else if (!parameterObject["includesingles"].asBoolean()) + musicUrl.AddOption("singles", false); + + bool allroles = false; + if (parameterObject["allroles"].isBoolean()) + allroles = parameterObject["allroles"].asBoolean(); + + const CVariant &filter = parameterObject["filter"]; + + if (allroles) + musicUrl.AddOption("roleid", -1000); //All roles, override implicit roleid=1 filter required for backward compatibility + else if (filter.isMember("roleid")) + musicUrl.AddOption("roleid", static_cast<int>(filter["roleid"].asInteger())); + else if (filter.isMember("role")) + musicUrl.AddOption("role", filter["role"].asString()); + // Only one of genreid/genre, artistid/artist, albumid/album or rules type filter is allowed by filter syntax + if (filter.isMember("artistid")) + musicUrl.AddOption("artistid", static_cast<int>(filter["artistid"].asInteger())); + else if (filter.isMember("artist")) + musicUrl.AddOption("artist", filter["artist"].asString()); + else if (filter.isMember("genreid")) + musicUrl.AddOption("genreid", static_cast<int>(filter["genreid"].asInteger())); + else if (filter.isMember("genre")) + musicUrl.AddOption("genre", filter["genre"].asString()); + else if (filter.isMember("albumid")) + musicUrl.AddOption("albumid", static_cast<int>(filter["albumid"].asInteger())); + else if (filter.isMember("album")) + musicUrl.AddOption("album", filter["album"].asString()); + else if (filter.isObject()) + { + std::string xsp; + if (!GetXspFiltering("songs", filter, xsp)) + return InvalidParams; + + musicUrl.AddOption("xsp", xsp); + } + + SortDescription sorting; + ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd); + if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes)) + return InvalidParams; + + int total; + std::set<std::string> fields; + if (parameterObject.isMember("properties") && parameterObject["properties"].isArray()) + { + for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array(); + field != parameterObject["properties"].end_array(); ++field) + fields.insert(field->asString()); + } + + if (!musicdatabase.GetSongsByWhereJSON(fields, musicUrl.ToString(), result, total, sorting)) + return InternalError; + + if (!result.isNull()) + { + bool bFetchArt = fields.find("art") != fields.end(); + bool bFetchFanart = fields.find("fanart") != fields.end(); + bool bFetchThumb = fields.find("thumbnail") != fields.end(); + if (bFetchArt || bFetchFanart || bFetchThumb) + { + CThumbLoader* thumbLoader = new CMusicThumbLoader(); + thumbLoader->OnLoaderStart(); + + std::set<std::string> artfields; + if (bFetchArt) + artfields.insert("art"); + if (bFetchFanart) + artfields.insert("fanart"); + if (bFetchThumb) + artfields.insert("thumbnail"); + + for (unsigned int index = 0; index < result["songs"].size(); index++) + { + CFileItem item; + // Only needs song and album id (if we have it) set to get art + // Getting art is quicker if "albumid" has been fetched + item.GetMusicInfoTag()->SetDatabaseId(result["songs"][index]["songid"].asInteger32(), MediaTypeSong); + if (result["songs"][index].isMember("albumid")) + item.GetMusicInfoTag()->SetAlbumId(result["songs"][index]["albumid"].asInteger32()); + else + item.GetMusicInfoTag()->SetAlbumId(-1); + + // Could use FillDetails, but it does unnecessary serialization of empty MusiInfoTag + // CFileItemPtr itemptr(new CFileItem(item)); + // FillDetails(item.GetMusicInfoTag(), itemptr, artfields, result["songs"][index], thumbLoader); + + thumbLoader->FillLibraryArt(item); + + if (bFetchThumb) + { + if (item.HasArt("thumb")) + result["songs"][index]["thumbnail"] = CTextureUtils::GetWrappedImageURL(item.GetArt("thumb")); + else + result["songs"][index]["thumbnail"] = ""; + } + if (bFetchFanart) + { + if (item.HasArt("fanart")) + result["songs"][index]["fanart"] = CTextureUtils::GetWrappedImageURL(item.GetArt("fanart")); + else + result["songs"][index]["fanart"] = ""; + } + if (bFetchArt) + { + CGUIListItem::ArtMap artMap = item.GetArt(); + CVariant artObj(CVariant::VariantTypeObject); + for (const auto& artIt : artMap) + { + if (!artIt.second.empty()) + artObj[artIt.first] = CTextureUtils::GetWrappedImageURL(artIt.second); + } + result["songs"][index]["art"] = artObj; + } + } + + delete thumbLoader; + } + } + + int start, end; + HandleLimits(parameterObject, result, total, start, end); + + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetSongDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int idSong = (int)parameterObject["songid"].asInteger(); + + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CSong song; + if (!musicdatabase.GetSong(idSong, song)) + return InvalidParams; + + CFileItemList items; + CFileItemPtr item = CFileItemPtr(new CFileItem(song)); + FillItemArtistIDs(song.GetArtistIDArray(), item); + items.Add(item); + + JSONRPC_STATUS ret = GetAdditionalSongDetails(parameterObject, items, musicdatabase); + if (ret != OK) + return ret; + + HandleFileItem("songid", true, "songdetails", items[0], parameterObject, parameterObject["properties"], result, false); + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetRecentlyAddedAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + VECALBUMS albums; + if (!musicdatabase.GetRecentlyAddedAlbums(albums)) + return InternalError; + + CFileItemList items; + for (unsigned int index = 0; index < albums.size(); index++) + { + std::string path = + StringUtils::Format("musicdb://recentlyaddedalbums/{}/", albums[index].idAlbum); + + CFileItemPtr item; + FillAlbumItem(albums[index], path, item); + items.Add(item); + } + + JSONRPC_STATUS ret = GetAdditionalAlbumDetails(parameterObject, items, musicdatabase); + if (ret != OK) + return ret; + + HandleFileItemList("albumid", false, "albums", items, parameterObject, result); + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetRecentlyAddedSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + int amount = (int)parameterObject["albumlimit"].asInteger(); + if (amount < 0) + amount = 0; + + CFileItemList items; + if (!musicdatabase.GetRecentlyAddedAlbumSongs("musicdb://songs/", items, (unsigned int)amount)) + return InternalError; + + JSONRPC_STATUS ret = GetAdditionalSongDetails(parameterObject, items, musicdatabase); + if (ret != OK) + return ret; + + HandleFileItemList("songid", true, "songs", items, parameterObject, result); + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetRecentlyPlayedAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + VECALBUMS albums; + if (!musicdatabase.GetRecentlyPlayedAlbums(albums)) + return InternalError; + + CFileItemList items; + for (unsigned int index = 0; index < albums.size(); index++) + { + std::string path = + StringUtils::Format("musicdb://recentlyplayedalbums/{}/", albums[index].idAlbum); + + CFileItemPtr item; + FillAlbumItem(albums[index], path, item); + items.Add(item); + } + + JSONRPC_STATUS ret = GetAdditionalAlbumDetails(parameterObject, items, musicdatabase); + if (ret != OK) + return ret; + + HandleFileItemList("albumid", false, "albums", items, parameterObject, result); + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetRecentlyPlayedSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CFileItemList items; + if (!musicdatabase.GetRecentlyPlayedAlbumSongs("musicdb://songs/", items)) + return InternalError; + + JSONRPC_STATUS ret = GetAdditionalSongDetails(parameterObject, items, musicdatabase); + if (ret != OK) + return ret; + + HandleFileItemList("songid", true, "songs", items, parameterObject, result); + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetGenres(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + // Check if sources for genre wanted + bool sourcesneeded(false); + std::set<std::string> checkProperties; + checkProperties.insert("sourceid"); + std::set<std::string> additionalProperties; + if (CheckForAdditionalProperties(parameterObject["properties"], checkProperties, additionalProperties)) + sourcesneeded = (additionalProperties.find("sourceid") != additionalProperties.end()); + + CFileItemList items; + if (!musicdatabase.GetGenresJSON(items, sourcesneeded)) + return InternalError; + + HandleFileItemList("genreid", false, "genres", items, parameterObject, result); + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetRoles(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CFileItemList items; + if (!musicdatabase.GetRolesNav("musicdb://songs/", items)) + return InternalError; + + /* need to set strTitle in each item*/ + for (unsigned int i = 0; i < (unsigned int)items.Size(); i++) + items[i]->GetMusicInfoTag()->SetTitle(items[i]->GetLabel()); + + HandleFileItemList("roleid", false, "roles", items, parameterObject, result); + return OK; +} + +JSONRPC_STATUS JSONRPC::CAudioLibrary::GetSources(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + // Add "file" to "properties" array by default + CVariant param = parameterObject; + if (!param.isMember("properties")) + param["properties"] = CVariant(CVariant::VariantTypeArray); + if (!param["properties"].isMember("file")) + param["properties"].append("file"); + + CFileItemList items; + if (!musicdatabase.GetSources(items)) + return InternalError; + + HandleFileItemList("sourceid", true, "sources", items, param, result); + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetAvailableArtTypes(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result) +{ + std::string mediaType; + int mediaID = -1; + if (parameterObject["item"].isMember("albumid")) + { + mediaType = MediaTypeAlbum; + mediaID = parameterObject["item"]["albumid"].asInteger32(); + } + if (parameterObject["item"].isMember("artistid")) + { + mediaType = MediaTypeArtist; + mediaID = parameterObject["item"]["artistid"].asInteger32(); + } + if (mediaID == -1) + return InternalError; + + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CVariant availablearttypes = CVariant(CVariant::VariantTypeArray); + for (const auto& artType : musicdatabase.GetAvailableArtTypesForItem(mediaID, mediaType)) + { + availablearttypes.append(artType); + } + result = CVariant(CVariant::VariantTypeObject); + result["availablearttypes"] = availablearttypes; + + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetAvailableArt(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result) +{ + std::string mediaType; + int mediaID = -1; + if (parameterObject["item"].isMember("albumid")) + { + mediaType = MediaTypeAlbum; + mediaID = parameterObject["item"]["albumid"].asInteger32(); + } + if (parameterObject["item"].isMember("artistid")) + { + mediaType = MediaTypeArtist; + mediaID = parameterObject["item"]["artistid"].asInteger32(); + } + if (mediaID == -1) + return InternalError; + + std::string artType = parameterObject["arttype"].asString(); + StringUtils::ToLower(artType); + + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CVariant availableart = CVariant(CVariant::VariantTypeArray); + for (const auto& artentry : musicdatabase.GetAvailableArtForItem(mediaID, mediaType, artType)) + { + CVariant item = CVariant(CVariant::VariantTypeObject); + item["url"] = CTextureUtils::GetWrappedImageURL(artentry.m_url); + item["arttype"] = artentry.m_aspect; + if (!artentry.m_preview.empty()) + item["previewurl"] = CTextureUtils::GetWrappedImageURL(artentry.m_preview); + availableart.append(item); + } + result = CVariant(CVariant::VariantTypeObject); + result["availableart"] = availableart; + + return OK; +} + +JSONRPC_STATUS CAudioLibrary::SetArtistDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["artistid"].asInteger(); + + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CArtist artist; + if (!musicdatabase.GetArtist(id, artist) || artist.idArtist <= 0) + return InvalidParams; + + if (ParameterNotNull(parameterObject, "artist")) + artist.strArtist = parameterObject["artist"].asString(); + if (ParameterNotNull(parameterObject, "instrument")) + CopyStringArray(parameterObject["instrument"], artist.instruments); + if (ParameterNotNull(parameterObject, "style")) + CopyStringArray(parameterObject["style"], artist.styles); + if (ParameterNotNull(parameterObject, "mood")) + CopyStringArray(parameterObject["mood"], artist.moods); + if (ParameterNotNull(parameterObject, "born")) + artist.strBorn = parameterObject["born"].asString(); + if (ParameterNotNull(parameterObject, "formed")) + artist.strFormed = parameterObject["formed"].asString(); + if (ParameterNotNull(parameterObject, "description")) + artist.strBiography = parameterObject["description"].asString(); + if (ParameterNotNull(parameterObject, "genre")) + CopyStringArray(parameterObject["genre"], artist.genre); + if (ParameterNotNull(parameterObject, "died")) + artist.strDied = parameterObject["died"].asString(); + if (ParameterNotNull(parameterObject, "disbanded")) + artist.strDisbanded = parameterObject["disbanded"].asString(); + if (ParameterNotNull(parameterObject, "yearsactive")) + CopyStringArray(parameterObject["yearsactive"], artist.yearsActive); + if (ParameterNotNull(parameterObject, "musicbrainzartistid")) + artist.strMusicBrainzArtistID = parameterObject["musicbrainzartistid"].asString(); + if (ParameterNotNull(parameterObject, "sortname")) + artist.strSortName = parameterObject["sortname"].asString(); + if (ParameterNotNull(parameterObject, "type")) + artist.strType = parameterObject["type"].asString(); + if (ParameterNotNull(parameterObject, "gender")) + artist.strGender = parameterObject["gender"].asString(); + if (ParameterNotNull(parameterObject, "disambiguation")) + artist.strDisambiguation = parameterObject["disambiguation"].asString(); + + // Update existing art. Any existing artwork that isn't specified in this request stays as is. + // If the value is null then the existing art with that type is removed. + if (ParameterNotNull(parameterObject, "art")) + { + // Get current artwork + musicdatabase.GetArtForItem(artist.idArtist, MediaTypeArtist, artist.art); + + std::set<std::string> removedArtwork; + CVariant art = parameterObject["art"]; + for (CVariant::const_iterator_map artIt = art.begin_map(); artIt != art.end_map(); ++artIt) + { + if (artIt->second.isString() && !artIt->second.asString().empty()) + artist.art[artIt->first] = CTextureUtils::UnwrapImageURL(artIt->second.asString()); + else if (artIt->second.isNull()) + { + artist.art.erase(artIt->first); + removedArtwork.insert(artIt->first); + } + } + // Remove null art now, as not done by update + if (!musicdatabase.RemoveArtForItem(artist.idArtist, MediaTypeArtist, removedArtwork)) + return InternalError; + } + + // Update artist including adding or replacing (but not removing) art + if (!musicdatabase.UpdateArtist(artist)) + return InternalError; + + CJSONRPCUtils::NotifyItemUpdated(); + return ACK; +} + +JSONRPC_STATUS CAudioLibrary::SetAlbumDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["albumid"].asInteger(); + + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CAlbum album; + // Get current album details, but not songs as we do not want to update them here + if (!musicdatabase.GetAlbum(id, album, false) || album.idAlbum <= 0) + return InvalidParams; + + if (ParameterNotNull(parameterObject, "title")) + album.strAlbum = parameterObject["title"].asString(); + if (ParameterNotNull(parameterObject, "displayartist")) + album.strArtistDesc = parameterObject["displayartist"].asString(); + // Set album sort string before processing artist credits + if (ParameterNotNull(parameterObject, "sortartist")) + album.strArtistSort = parameterObject["sortartist"].asString(); + + // Match up artist names and mbids to make new artist credits + // Mbid values only apply if there are names + if (ParameterNotNull(parameterObject, "artist")) + { + std::vector<std::string> artists; + std::vector<std::string> mbids; + CopyStringArray(parameterObject["artist"], artists); + // Check for Musicbrainz ids + if (ParameterNotNull(parameterObject, "musicbrainzalbumartistid")) + CopyStringArray(parameterObject["musicbrainzalbumartistid"], mbids); + // When display artist is not provided and yet artists is changing make by concatenation + if (!ParameterNotNull(parameterObject, "displayartist")) + album.strArtistDesc = StringUtils::Join(artists, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator); + album.SetArtistCredits(artists, std::vector<std::string>(), mbids); + // On updatealbum artists will be changed + album.bArtistSongMerge = true; + } + + if (ParameterNotNull(parameterObject, "description")) + album.strReview = parameterObject["description"].asString(); + if (ParameterNotNull(parameterObject, "genre")) + CopyStringArray(parameterObject["genre"], album.genre); + if (ParameterNotNull(parameterObject, "theme")) + CopyStringArray(parameterObject["theme"], album.themes); + if (ParameterNotNull(parameterObject, "mood")) + CopyStringArray(parameterObject["mood"], album.moods); + if (ParameterNotNull(parameterObject, "style")) + CopyStringArray(parameterObject["style"], album.styles); + if (ParameterNotNull(parameterObject, "type")) + album.strType = parameterObject["type"].asString(); + if (ParameterNotNull(parameterObject, "albumlabel")) + album.strLabel = parameterObject["albumlabel"].asString(); + if (ParameterNotNull(parameterObject, "rating")) + album.fRating = parameterObject["rating"].asFloat(); + if (ParameterNotNull(parameterObject, "userrating")) + album.iUserrating = static_cast<int>(parameterObject["userrating"].asInteger()); + if (ParameterNotNull(parameterObject, "votes")) + album.iVotes = static_cast<int>(parameterObject["votes"].asInteger()); + if (ParameterNotNull(parameterObject, "year")) + album.strReleaseDate = parameterObject["year"].asString(); + if (ParameterNotNull(parameterObject, "musicbrainzalbumid")) + album.strMusicBrainzAlbumID = parameterObject["musicbrainzalbumid"].asString(); + if (ParameterNotNull(parameterObject, "musicbrainzreleasegroupid")) + album.strReleaseGroupMBID = parameterObject["musicbrainzreleasegroupid"].asString(); + if (ParameterNotNull(parameterObject, "isboxset")) + album.bBoxedSet = parameterObject["isboxset"].asBoolean(); + if (ParameterNotNull(parameterObject, "originaldate")) + album.strOrigReleaseDate = parameterObject["originaldate"].asString(); + if (ParameterNotNull(parameterObject, "releasedate")) + album.strReleaseDate = parameterObject["releasedate"].asString(); + if (ParameterNotNull(parameterObject, "albumstatus")) + album.strReleaseStatus = parameterObject["albumstatus"].asString(); + + // Update existing art. Any existing artwork that isn't specified in this request stays as is. + // If the value is null then the existing art with that type is removed. + if (ParameterNotNull(parameterObject, "art")) + { + // Get current artwork + musicdatabase.GetArtForItem(album.idAlbum, MediaTypeAlbum, album.art); + + std::set<std::string> removedArtwork; + CVariant art = parameterObject["art"]; + for (CVariant::const_iterator_map artIt = art.begin_map(); artIt != art.end_map(); ++artIt) + { + if (artIt->second.isString() && !artIt->second.asString().empty()) + album.art[artIt->first] = CTextureUtils::UnwrapImageURL(artIt->second.asString()); + else if (artIt->second.isNull()) + { + album.art.erase(artIt->first); + removedArtwork.insert(artIt->first); + } + } + // Remove null art now, as not done by update + if (!musicdatabase.RemoveArtForItem(album.idAlbum, MediaTypeAlbum, removedArtwork)) + return InternalError; + } + + // Update artist including adding or replacing (but not removing) art + if (!musicdatabase.UpdateAlbum(album)) + return InternalError; + + CJSONRPCUtils::NotifyItemUpdated(); + return ACK; +} + +JSONRPC_STATUS CAudioLibrary::SetSongDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["songid"].asInteger(); + + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CSong song; + if (!musicdatabase.GetSong(id, song) || song.idSong != id) + return InvalidParams; + + if (ParameterNotNull(parameterObject, "title")) + song.strTitle = parameterObject["title"].asString(); + + if (ParameterNotNull(parameterObject, "displayartist")) + song.strArtistDesc = parameterObject["displayartist"].asString(); + // Set album sort string before processing artist credits + if (ParameterNotNull(parameterObject, "sortartist")) + song.strArtistSort = parameterObject["sortartist"].asString(); + + // Match up artist names and mbids to make new artist credits + // Mbid values only apply if there are names + bool updateartists = false; + if (ParameterNotNull(parameterObject, "artist")) + { + std::vector<std::string> artists, mbids; + updateartists = true; + CopyStringArray(parameterObject["artist"], artists); + // Check for Musicbrainz ids + if (ParameterNotNull(parameterObject, "musicbrainzartistid")) + CopyStringArray(parameterObject["musicbrainzartistid"], mbids); + // When display artist is not provided and yet artists is changing make by concatenation + if (!ParameterNotNull(parameterObject, "displayartist")) + song.strArtistDesc = StringUtils::Join(artists, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator); + song.SetArtistCredits(artists, std::vector<std::string>(), mbids); + } + + if (ParameterNotNull(parameterObject, "genre")) + CopyStringArray(parameterObject["genre"], song.genre); + if (ParameterNotNull(parameterObject, "year")) + song.strReleaseDate = parameterObject["year"].asString(); + if (ParameterNotNull(parameterObject, "rating")) + song.rating = parameterObject["rating"].asFloat(); + if (ParameterNotNull(parameterObject, "userrating")) + song.userrating = static_cast<int>(parameterObject["userrating"].asInteger()); + if (ParameterNotNull(parameterObject, "track")) + song.iTrack = (song.iTrack & 0xffff0000) | ((int)parameterObject["track"].asInteger() & 0xffff); + if (ParameterNotNull(parameterObject, "disc")) + song.iTrack = (song.iTrack & 0xffff) | ((int)parameterObject["disc"].asInteger() << 16); + if (ParameterNotNull(parameterObject, "duration")) + song.iDuration = (int)parameterObject["duration"].asInteger(); + if (ParameterNotNull(parameterObject, "comment")) + song.strComment = parameterObject["comment"].asString(); + if (ParameterNotNull(parameterObject, "musicbrainztrackid")) + song.strMusicBrainzTrackID = parameterObject["musicbrainztrackid"].asString(); + if (ParameterNotNull(parameterObject, "playcount")) + song.iTimesPlayed = static_cast<int>(parameterObject["playcount"].asInteger()); + if (ParameterNotNull(parameterObject, "lastplayed")) + song.lastPlayed.SetFromDBDateTime(parameterObject["lastplayed"].asString()); + if (ParameterNotNull(parameterObject, "mood")) + song.strMood = parameterObject["mood"].asString(); + if (ParameterNotNull(parameterObject, "disctitle")) + song.strDiscSubtitle = parameterObject["disctitle"].asString(); + if (ParameterNotNull(parameterObject, "bpm")) + song.iBPM = static_cast<int>(parameterObject["bpm"].asInteger()); + if (ParameterNotNull(parameterObject, "originaldate")) + song.strOrigReleaseDate = parameterObject["originaldate"].asString(); + if (ParameterNotNull(parameterObject, "albumreleasedate")) + song.strReleaseDate = parameterObject["albumreleasedate"].asString(); + + // Update existing art. Any existing artwork that isn't specified in this request stays as is. + // If the value is null then the existing art with that type is removed. + if (ParameterNotNull(parameterObject, "art")) + { + // Get current artwork + std::map<std::string, std::string> artwork; + musicdatabase.GetArtForItem(song.idSong, MediaTypeSong, artwork); + + std::set<std::string> removedArtwork; + CVariant art = parameterObject["art"]; + for (CVariant::const_iterator_map artIt = art.begin_map(); artIt != art.end_map(); ++artIt) + { + if (artIt->second.isString() && !artIt->second.asString().empty()) + artwork[artIt->first] = CTextureUtils::UnwrapImageURL(artIt->second.asString()); + else if (artIt->second.isNull()) + { + artwork.erase(artIt->first); + removedArtwork.insert(artIt->first); + } + } + //Update artwork, not done in update song + musicdatabase.SetArtForItem(song.idSong, MediaTypeSong, artwork); + if (!musicdatabase.RemoveArtForItem(song.idSong, MediaTypeSong, removedArtwork)) + return InternalError; + } + + // Update song (not including artwork) + if (!musicdatabase.UpdateSong(song, updateartists)) + return InternalError; + + CJSONRPCUtils::NotifyItemUpdated(); + return ACK; +} + +JSONRPC_STATUS CAudioLibrary::Scan(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string directory = parameterObject["directory"].asString(); + std::string cmd = + StringUtils::Format("updatelibrary(music, {}, {})", StringUtils::Paramify(directory), + parameterObject["showdialogs"].asBoolean() ? "true" : "false"); + + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd); + return ACK; +} + +JSONRPC_STATUS CAudioLibrary::Export(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string cmd; + if (parameterObject["options"].isMember("path")) + cmd = StringUtils::Format("exportlibrary2(music, singlefile, {}, albums, albumartists)", + StringUtils::Paramify(parameterObject["options"]["path"].asString())); + else + { + cmd = "exportlibrary2(music, library, dummy, albums, albumartists"; + if (parameterObject["options"]["images"].isBoolean() && + parameterObject["options"]["images"].asBoolean() == true) + cmd += ", artwork"; + if (parameterObject["options"]["overwrite"].isBoolean() && + parameterObject["options"]["overwrite"].asBoolean() == true) + cmd += ", overwrite"; + cmd += ")"; + } + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd); + return ACK; +} + +JSONRPC_STATUS CAudioLibrary::Clean(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string cmd = StringUtils::Format( + "cleanlibrary(music, {})", parameterObject["showdialogs"].asBoolean() ? "true" : "false"); + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd); + return ACK; +} + +bool CAudioLibrary::FillFileItem( + const std::string& strFilename, + std::shared_ptr<CFileItem>& item, + const CVariant& parameterObject /* = CVariant(CVariant::VariantTypeArray) */) +{ + CMusicDatabase musicdatabase; + if (strFilename.empty()) + return false; + + bool filled = false; + if (musicdatabase.Open()) + { + if (CDirectory::Exists(strFilename)) + { + CAlbum album; + int albumid = musicdatabase.GetAlbumIdByPath(strFilename); + if (musicdatabase.GetAlbum(albumid, album, false)) + { + item->SetFromAlbum(album); + FillItemArtistIDs(album.GetArtistIDArray(), item); + + CFileItemList items; + items.Add(item); + + if (GetAdditionalAlbumDetails(parameterObject, items, musicdatabase) == OK) + filled = true; + } + } + else + { + CSong song; + if (musicdatabase.GetSongByFileName(strFilename, song)) + { + item->SetFromSong(song); + FillItemArtistIDs(song.GetArtistIDArray(), item); + + CFileItemList items; + items.Add(item); + if (GetAdditionalSongDetails(parameterObject, items, musicdatabase) == OK) + filled = true; + } + } + } + + if (item->GetLabel().empty()) + { + item->SetLabel(CUtil::GetTitleFromPath(strFilename, false)); + if (item->GetLabel().empty()) + item->SetLabel(URIUtils::GetFileName(strFilename)); + } + + return filled; +} + +bool CAudioLibrary::FillFileItemList(const CVariant ¶meterObject, CFileItemList &list) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return false; + + std::string file = parameterObject["file"].asString(); + int artistID = (int)parameterObject["artistid"].asInteger(-1); + int albumID = (int)parameterObject["albumid"].asInteger(-1); + int genreID = (int)parameterObject["genreid"].asInteger(-1); + + bool success = false; + CFileItemPtr fileItem(new CFileItem()); + if (FillFileItem(file, fileItem, parameterObject)) + { + success = true; + list.Add(fileItem); + } + + if (artistID != -1 || albumID != -1 || genreID != -1) + success |= musicdatabase.GetSongsNav("musicdb://songs/", list, genreID, artistID, albumID); + + int songID = (int)parameterObject["songid"].asInteger(-1); + if (songID != -1) + { + CSong song; + if (musicdatabase.GetSong(songID, song)) + { + list.Add(CFileItemPtr(new CFileItem(song))); + success = true; + } + } + + if (success) + { + // If we retrieved the list of songs by "artistid" + // we sort by album (and implicitly by track number) + if (artistID != -1) + list.Sort(SortByAlbum, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); + // If we retrieve the list of songs by "genreid" + // we sort by artist (and implicitly by album and track number) + else if (genreID != -1) + list.Sort(SortByArtist, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); + // otherwise we sort by track number + else + list.Sort(SortByTrackNumber, SortOrderAscending); + + } + + return success; +} + +void CAudioLibrary::FillItemArtistIDs(const std::vector<int>& artistids, + std::shared_ptr<CFileItem>& item) +{ + // Add artistIds as separate property as not part of CMusicInfoTag + CVariant artistidObj(CVariant::VariantTypeArray); + for (const auto& artistid : artistids) + artistidObj.push_back(artistid); + + item->SetProperty("artistid", artistidObj); +} + +void CAudioLibrary::FillAlbumItem(const CAlbum& album, + const std::string& path, + std::shared_ptr<CFileItem>& item) +{ + item = CFileItemPtr(new CFileItem(path, album)); + // Add album artistIds as separate property as not part of CMusicInfoTag + std::vector<int> artistids = album.GetArtistIDArray(); + FillItemArtistIDs(artistids, item); +} + +JSONRPC_STATUS CAudioLibrary::GetAdditionalDetails(const CVariant ¶meterObject, CFileItemList &items) +{ + if (items.IsEmpty()) + return OK; + + CMusicDatabase musicdb; + if (CMediaTypes::IsMediaType(items.GetContent(), MediaTypeArtist)) + return GetAdditionalArtistDetails(parameterObject, items, musicdb); + else if (CMediaTypes::IsMediaType(items.GetContent(), MediaTypeAlbum)) + return GetAdditionalAlbumDetails(parameterObject, items, musicdb); + else if (CMediaTypes::IsMediaType(items.GetContent(), MediaTypeSong)) + return GetAdditionalSongDetails(parameterObject, items, musicdb); + + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetAdditionalArtistDetails(const CVariant& parameterObject, + const CFileItemList& items, + CMusicDatabase& musicdatabase) +{ + if (!musicdatabase.Open()) + return InternalError; + + std::set<std::string> checkProperties; + checkProperties.insert("roles"); + checkProperties.insert("songgenres"); + checkProperties.insert("isalbumartist"); + checkProperties.insert("sourceid"); + std::set<std::string> additionalProperties; + if (!CheckForAdditionalProperties(parameterObject["properties"], checkProperties, additionalProperties)) + return OK; + + if (additionalProperties.find("roles") != additionalProperties.end()) + { + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + musicdatabase.GetRolesByArtist(item->GetMusicInfoTag()->GetDatabaseId(), item.get()); + } + } + if (additionalProperties.find("songgenres") != additionalProperties.end()) + { + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + musicdatabase.GetGenresByArtist(item->GetMusicInfoTag()->GetDatabaseId(), item.get()); + } + } + if (additionalProperties.find("isalbumartist") != additionalProperties.end()) + { + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + musicdatabase.GetIsAlbumArtist(item->GetMusicInfoTag()->GetDatabaseId(), item.get()); + } + } + if (additionalProperties.find("sourceid") != additionalProperties.end()) + { + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + musicdatabase.GetSourcesByArtist(item->GetMusicInfoTag()->GetDatabaseId(), item.get()); + } + } + + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetAdditionalAlbumDetails(const CVariant& parameterObject, + const CFileItemList& items, + CMusicDatabase& musicdatabase) +{ + if (!musicdatabase.Open()) + return InternalError; + + std::set<std::string> checkProperties; + checkProperties.insert("songgenres"); + checkProperties.insert("sourceid"); + std::set<std::string> additionalProperties; + if (!CheckForAdditionalProperties(parameterObject["properties"], checkProperties, additionalProperties)) + return OK; + + if (additionalProperties.find("songgenres") != additionalProperties.end()) + { + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + musicdatabase.GetGenresByAlbum(item->GetMusicInfoTag()->GetDatabaseId(), item.get()); + } + } + if (additionalProperties.find("sourceid") != additionalProperties.end()) + { + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + musicdatabase.GetSourcesByAlbum(item->GetMusicInfoTag()->GetDatabaseId(), item.get()); + } + } + + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetAdditionalSongDetails(const CVariant& parameterObject, + const CFileItemList& items, + CMusicDatabase& musicdatabase) +{ + if (!musicdatabase.Open()) + return InternalError; + + std::set<std::string> checkProperties; + checkProperties.insert("genreid"); + checkProperties.insert("sourceid"); + // Query (songview join songartistview) returns song.strAlbumArtists = CMusicInfoTag.m_strAlbumArtistDesc only + // Actual album artist data, if required, comes from album_artist and artist tables. + // It may differ from just splitting album artist description string + checkProperties.insert("albumartist"); + checkProperties.insert("albumartistid"); + checkProperties.insert("musicbrainzalbumartistid"); + std::set<std::string> additionalProperties; + if (!CheckForAdditionalProperties(parameterObject["properties"], checkProperties, additionalProperties)) + return OK; + + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + if (additionalProperties.find("genreid") != additionalProperties.end()) + { + std::vector<int> genreids; + if (musicdatabase.GetGenresBySong(item->GetMusicInfoTag()->GetDatabaseId(), genreids)) + { + CVariant genreidObj(CVariant::VariantTypeArray); + for (const auto& genreid : genreids) + genreidObj.push_back(genreid); + + item->SetProperty("genreid", genreidObj); + } + } + if (additionalProperties.find("sourceid") != additionalProperties.end()) + { + musicdatabase.GetSourcesBySong(item->GetMusicInfoTag()->GetDatabaseId(), item->GetPath(), item.get()); + } + if (item->GetMusicInfoTag()->GetAlbumId() > 0) + { + if (additionalProperties.find("albumartist") != additionalProperties.end() || + additionalProperties.find("albumartistid") != additionalProperties.end() || + additionalProperties.find("musicbrainzalbumartistid") != additionalProperties.end()) + { + musicdatabase.GetArtistsByAlbum(item->GetMusicInfoTag()->GetAlbumId(), item.get()); + } + } + } + + return OK; +} + +bool CAudioLibrary::CheckForAdditionalProperties(const CVariant &properties, const std::set<std::string> &checkProperties, std::set<std::string> &foundProperties) +{ + if (!properties.isArray() || properties.empty()) + return false; + + std::set<std::string> checkingProperties = checkProperties; + for (CVariant::const_iterator_array itr = properties.begin_array(); + itr != properties.end_array() && !checkingProperties.empty(); ++itr) + { + if (!itr->isString()) + continue; + + std::string property = itr->asString(); + if (checkingProperties.find(property) != checkingProperties.end()) + { + checkingProperties.erase(property); + foundProperties.insert(property); + } + } + + return !foundProperties.empty(); +} diff --git a/xbmc/interfaces/json-rpc/AudioLibrary.h b/xbmc/interfaces/json-rpc/AudioLibrary.h new file mode 100644 index 0000000..9946f9d --- /dev/null +++ b/xbmc/interfaces/json-rpc/AudioLibrary.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "FileItemHandler.h" +#include "JSONRPC.h" + +#include <memory> +#include <set> +#include <string> +#include <vector> + +class CAlbum; +class CFileitem; +class CFileitemList; +class CMusicDatabase; +class CVariant; + +namespace JSONRPC +{ + class CAudioLibrary : public CFileItemHandler + { + public: + static JSONRPC_STATUS GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetArtists(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetArtistDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetAlbumDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetSongDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetGenres(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetRoles(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetSources(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetAvailableArtTypes(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result); + static JSONRPC_STATUS GetAvailableArt(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result); + + static JSONRPC_STATUS GetRecentlyAddedAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetRecentlyAddedSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetRecentlyPlayedAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetRecentlyPlayedSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS SetArtistDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetAlbumDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetSongDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS Scan(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Export(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Clean(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static bool FillFileItem( + const std::string& strFilename, + std::shared_ptr<CFileItem>& item, + const CVariant& parameterObject = CVariant(CVariant::VariantTypeArray)); + static bool FillFileItemList(const CVariant ¶meterObject, CFileItemList &list); + + static JSONRPC_STATUS GetAdditionalDetails(const CVariant ¶meterObject, CFileItemList &items); + static JSONRPC_STATUS GetAdditionalArtistDetails(const CVariant& parameterObject, + const CFileItemList& items, + CMusicDatabase& musicdatabase); + static JSONRPC_STATUS GetAdditionalAlbumDetails(const CVariant& parameterObject, + const CFileItemList& items, + CMusicDatabase& musicdatabase); + static JSONRPC_STATUS GetAdditionalSongDetails(const CVariant& parameterObject, + const CFileItemList& items, + CMusicDatabase& musicdatabase); + + private: + static void FillAlbumItem(const CAlbum& album, + const std::string& path, + std::shared_ptr<CFileItem>& item); + static void FillItemArtistIDs(const std::vector<int>& artistids, + std::shared_ptr<CFileItem>& item); + + static bool CheckForAdditionalProperties(const CVariant &properties, const std::set<std::string> &checkProperties, std::set<std::string> &foundProperties); + }; +} diff --git a/xbmc/interfaces/json-rpc/CMakeLists.txt b/xbmc/interfaces/json-rpc/CMakeLists.txt new file mode 100644 index 0000000..59ced89 --- /dev/null +++ b/xbmc/interfaces/json-rpc/CMakeLists.txt @@ -0,0 +1,48 @@ +set(SOURCES AddonsOperations.cpp + ApplicationOperations.cpp + AudioLibrary.cpp + FavouritesOperations.cpp + FileItemHandler.cpp + FileOperations.cpp + GUIOperations.cpp + InputOperations.cpp + JSONRPC.cpp + JSONServiceDescription.cpp + JSONUtils.cpp + PlayerOperations.cpp + PlaylistOperations.cpp + ProfilesOperations.cpp + PVROperations.cpp + SettingsOperations.cpp + SystemOperations.cpp + TextureOperations.cpp + VideoLibrary.cpp + XBMCOperations.cpp) + +set(HEADERS AddonsOperations.h + ApplicationOperations.h + AudioLibrary.h + FavouritesOperations.h + FileItemHandler.h + FileOperations.h + GUIOperations.h + IClient.h + IJSONRPCAnnouncer.h + InputOperations.h + ITransportLayer.h + JSONRPC.h + JSONRPCUtils.h + JSONServiceDescription.h + JSONUtils.h + PlayerOperations.h + PlaylistOperations.h + ProfilesOperations.h + PVROperations.h + SettingsOperations.h + SystemOperations.h + TextureOperations.h + VideoLibrary.h + XBMCOperations.h) + +core_add_library(jsonrpc_interface) +add_dependencies(${CORE_LIBRARY} generate_json_header) diff --git a/xbmc/interfaces/json-rpc/FavouritesOperations.cpp b/xbmc/interfaces/json-rpc/FavouritesOperations.cpp new file mode 100644 index 0000000..99277e5 --- /dev/null +++ b/xbmc/interfaces/json-rpc/FavouritesOperations.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "FavouritesOperations.h" + +#include "ServiceBroker.h" +#include "favourites/FavouritesService.h" +#include "favourites/FavouritesURL.h" +#include "guilib/WindowIDs.h" +#include "input/WindowTranslator.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" + +#include <vector> + +using namespace JSONRPC; + +JSONRPC_STATUS CFavouritesOperations::GetFavourites(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CFileItemList favourites; + CServiceBroker::GetFavouritesService().GetAll(favourites); + + std::string type = !parameterObject["type"].isNull() ? parameterObject["type"].asString() : ""; + + std::set<std::string> fields; + if (parameterObject.isMember("properties") && parameterObject["properties"].isArray()) + { + for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array(); + field != parameterObject["properties"].end_array(); ++field) + fields.insert(field->asString()); + } + + for (int i = 0; i < favourites.Size(); i++) + { + CVariant object; + CFileItemPtr item = favourites.Get(i); + + const CFavouritesURL url(item->GetPath()); + if (!url.IsValid()) + continue; + + const CFavouritesURL::Action function = url.GetAction(); + + object["title"] = item->GetLabel(); + if (fields.find("thumbnail") != fields.end()) + object["thumbnail"] = item->GetArt("thumb"); + + if (function == CFavouritesURL::Action::ACTIVATE_WINDOW) + { + object["type"] = "window"; + if (fields.find("window") != fields.end()) + { + object["window"] = CWindowTranslator::TranslateWindow(url.GetWindowID()); + } + if (fields.find("windowparameter") != fields.end()) + { + object["windowparameter"] = url.GetTarget(); + } + } + else if (function == CFavouritesURL::Action::PLAY_MEDIA) + { + object["type"] = "media"; + if (fields.find("path") != fields.end()) + object["path"] = url.GetTarget(); + } + else if (function == CFavouritesURL::Action::RUN_SCRIPT) + { + object["type"] = "script"; + if (fields.find("path") != fields.end()) + object["path"] = url.GetTarget(); + } + else if (function == CFavouritesURL::Action::START_ANDROID_ACTIVITY) + { + object["type"] = "androidapp"; + if (fields.find("path") != fields.end()) + object["path"] = url.GetTarget(); + } + else + object["type"] = "unknown"; + + if (type.empty() || type.compare(object["type"].asString()) == 0) + result["favourites"].append(object); + } + + int start, end; + HandleLimits(parameterObject, result, result["favourites"].size(), start, end); + + return OK; +} + +JSONRPC_STATUS CFavouritesOperations::AddFavourite(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string type = parameterObject["type"].asString(); + + if (type.compare("unknown") == 0) + return InvalidParams; + + if ((type.compare("media") == 0 || type.compare("script") == 0 || type.compare("androidapp") == 0) && !ParameterNotNull(parameterObject, "path")) + { + result["method"] = "Favourites.AddFavourite"; + result["stack"]["message"] = "Missing parameter"; + result["stack"]["name"] = "path"; + result["stack"]["type"] = "string"; + return InvalidParams; + } + + if (type.compare("window") == 0 && !ParameterNotNull(parameterObject, "window")) + { + result["method"] = "Favourites.AddFavourite"; + result["stack"]["message"] = "Missing parameter"; + result["stack"]["name"] = "window"; + result["stack"]["type"] = "string"; + return InvalidParams; + } + + std::string title = parameterObject["title"].asString(); + std::string path = parameterObject["path"].asString(); + + CFileItem item; + int contextWindow = 0; + if (type.compare("window") == 0) + { + item = CFileItem(parameterObject["windowparameter"].asString(), true); + contextWindow = CWindowTranslator::TranslateWindow(parameterObject["window"].asString()); + if (contextWindow == WINDOW_INVALID) + return InvalidParams; + } + else if (type.compare("script") == 0) + { + if (!URIUtils::IsScript(path)) + path = "script://" + path; + item = CFileItem(path, false); + } + else if (type.compare("androidapp") == 0) + { + if (!URIUtils::IsAndroidApp(path)) + path = "androidapp://" + path; + item = CFileItem(path, false); + } + else if (type.compare("media") == 0) + { + item = CFileItem(path, false); + } + else + return InvalidParams; + + item.SetLabel(title); + if (ParameterNotNull(parameterObject,"thumbnail")) + item.SetArt("thumb", parameterObject["thumbnail"].asString()); + + if (CServiceBroker::GetFavouritesService().AddOrRemove(item, contextWindow)) + return ACK; + else + return FailedToExecute; +} diff --git a/xbmc/interfaces/json-rpc/FavouritesOperations.h b/xbmc/interfaces/json-rpc/FavouritesOperations.h new file mode 100644 index 0000000..bb24b61 --- /dev/null +++ b/xbmc/interfaces/json-rpc/FavouritesOperations.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPC.h" + +class CVariant; + +namespace JSONRPC +{ + class CFavouritesOperations : public CJSONUtils + { + public: + static JSONRPC_STATUS GetFavourites(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS AddFavourite(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + }; +} diff --git a/xbmc/interfaces/json-rpc/FileItemHandler.cpp b/xbmc/interfaces/json-rpc/FileItemHandler.cpp new file mode 100644 index 0000000..9d649d3 --- /dev/null +++ b/xbmc/interfaces/json-rpc/FileItemHandler.cpp @@ -0,0 +1,554 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "FileItemHandler.h" + +#include "AudioLibrary.h" +#include "FileOperations.h" +#include "ServiceBroker.h" +#include "TextureDatabase.h" +#include "Util.h" +#include "VideoLibrary.h" +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_epg.h" // EPG_TAG_INVALID_UID +#include "filesystem/Directory.h" +#include "music/MusicThumbLoader.h" +#include "music/tags/MusicInfoTag.h" +#include "pictures/PictureInfoTag.h" +#include "pvr/PVRManager.h" +#include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRChannelGroupMember.h" +#include "pvr/epg/EpgInfoTag.h" +#include "pvr/recordings/PVRRecording.h" +#include "pvr/recordings/PVRRecordings.h" +#include "pvr/timers/PVRTimerInfoTag.h" +#include "pvr/timers/PVRTimers.h" +#include "utils/FileUtils.h" +#include "utils/ISerializable.h" +#include "utils/SortUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "video/VideoDatabase.h" +#include "video/VideoInfoTag.h" +#include "video/VideoThumbLoader.h" + +#include <map> +#include <string.h> + +using namespace MUSIC_INFO; +using namespace JSONRPC; +using namespace XFILE; + +bool CFileItemHandler::GetField(const std::string& field, + const CVariant& info, + const std::shared_ptr<CFileItem>& item, + CVariant& result, + bool& fetchedArt, + CThumbLoader* thumbLoader /* = NULL */) +{ + if (result.isMember(field) && !result[field].empty()) + return true; + + // overwrite serialized values + if (item) + { + if (field == "mimetype" && item->GetMimeType().empty()) + { + item->FillInMimeType(false); + result[field] = item->GetMimeType(); + return true; + } + + if (item->HasPVRChannelInfoTag()) + { + // Translate PVR.Details.Broadcast -> List.Item.Base format + if (field == "cast") + { + // string -> Video.Cast + const std::vector<std::string> actors = + StringUtils::Split(info[field].asString(), EPG_STRING_TOKEN_SEPARATOR); + + result[field] = CVariant(CVariant::VariantTypeArray); + for (const auto& actor : actors) + { + CVariant actorVar; + actorVar["name"] = actor; + result[field].push_back(actorVar); + } + return true; + } + else if (field == "director" || field == "writer") + { + // string -> Array.String + result[field] = StringUtils::Split(info[field].asString(), EPG_STRING_TOKEN_SEPARATOR); + return true; + } + else if (field == "isrecording") + { + result[field] = CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel( + *item->GetPVRChannelInfoTag()); + return true; + } + } + + if (item->HasEPGInfoTag()) + { + if (field == "hastimer") + { + const std::shared_ptr<PVR::CPVRTimerInfoTag> timer = + CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(item->GetEPGInfoTag()); + result[field] = (timer != nullptr); + return true; + } + else if (field == "hasreminder") + { + const std::shared_ptr<PVR::CPVRTimerInfoTag> timer = + CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(item->GetEPGInfoTag()); + result[field] = (timer && timer->IsReminder()); + return true; + } + else if (field == "hastimerrule") + { + const std::shared_ptr<PVR::CPVRTimerInfoTag> timer = + CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(item->GetEPGInfoTag()); + result[field] = (timer && timer->HasParent()); + return true; + } + else if (field == "hasrecording") + { + const std::shared_ptr<PVR::CPVRRecording> recording = + CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag( + item->GetEPGInfoTag()); + result[field] = (recording != nullptr); + return true; + } + else if (field == "recording") + { + const std::shared_ptr<PVR::CPVRRecording> recording = + CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag( + item->GetEPGInfoTag()); + result[field] = recording ? recording->m_strFileNameAndPath : ""; + return true; + } + } + } + + // check for serialized values + if (info.isMember(field) && !info[field].isNull()) + { + result[field] = info[field]; + return true; + } + + // check if the field requires special handling + if (item) + { + if (item->IsAlbum()) + { + if (field == "albumlabel") + { + result[field] = item->GetProperty("album_label"); + return true; + } + if (item->HasProperty("album_" + field + "_array")) + { + result[field] = item->GetProperty("album_" + field + "_array"); + return true; + } + if (item->HasProperty("album_" + field)) + { + result[field] = item->GetProperty("album_" + field); + return true; + } + } + + if (item->HasProperty("artist_" + field + "_array")) + { + result[field] = item->GetProperty("artist_" + field + "_array"); + return true; + } + if (item->HasProperty("artist_" + field)) + { + result[field] = item->GetProperty("artist_" + field); + return true; + } + + if (field == "art") + { + if (thumbLoader && !item->GetProperty("libraryartfilled").asBoolean() && !fetchedArt && + ((item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_iDbId > -1) || + (item->HasMusicInfoTag() && item->GetMusicInfoTag()->GetDatabaseId() > -1))) + { + thumbLoader->FillLibraryArt(*item); + fetchedArt = true; + } + + CGUIListItem::ArtMap artMap = item->GetArt(); + CVariant artObj(CVariant::VariantTypeObject); + for (const auto& artIt : artMap) + { + if (!artIt.second.empty()) + artObj[artIt.first] = CTextureUtils::GetWrappedImageURL(artIt.second); + } + + result["art"] = artObj; + return true; + } + + if (field == "thumbnail") + { + if (thumbLoader != NULL && !item->HasArt("thumb") && !fetchedArt && + ((item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_iDbId > -1) || (item->HasMusicInfoTag() && item->GetMusicInfoTag()->GetDatabaseId() > -1))) + { + thumbLoader->FillLibraryArt(*item); + fetchedArt = true; + } + else if (item->HasPictureInfoTag() && !item->HasArt("thumb")) + item->SetArt("thumb", CTextureUtils::GetWrappedThumbURL(item->GetPath())); + + if (item->HasArt("thumb")) + result["thumbnail"] = CTextureUtils::GetWrappedImageURL(item->GetArt("thumb")); + else + result["thumbnail"] = ""; + + return true; + } + + if (field == "fanart") + { + if (thumbLoader != NULL && !item->HasArt("fanart") && !fetchedArt && + ((item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_iDbId > -1) || (item->HasMusicInfoTag() && item->GetMusicInfoTag()->GetDatabaseId() > -1))) + { + thumbLoader->FillLibraryArt(*item); + fetchedArt = true; + } + + if (item->HasArt("fanart")) + result["fanart"] = CTextureUtils::GetWrappedImageURL(item->GetArt("fanart")); + else + result["fanart"] = ""; + + return true; + } + + if (item->HasVideoInfoTag() && item->GetVideoContentType() == VideoDbContentType::TVSHOWS) + { + if (item->GetVideoInfoTag()->m_iSeason < 0 && field == "season") + { + result[field] = (int)item->GetProperty("totalseasons").asInteger(); + return true; + } + if (field == "watchedepisodes") + { + result[field] = (int)item->GetProperty("watchedepisodes").asInteger(); + return true; + } + } + + if (item->HasProperty(field)) + { + result[field] = item->GetProperty(field); + return true; + } + } + + return false; +} + +void CFileItemHandler::FillDetails(const ISerializable* info, + const std::shared_ptr<CFileItem>& item, + std::set<std::string>& fields, + CVariant& result, + CThumbLoader* thumbLoader /* = NULL */) +{ + if (info == NULL || fields.empty()) + return; + + CVariant serialization; + info->Serialize(serialization); + + bool fetchedArt = false; + + std::set<std::string> originalFields = fields; + + for (const auto& fieldIt : originalFields) + { + if (GetField(fieldIt, serialization, item, result, fetchedArt, thumbLoader) && + result.isMember(fieldIt) && !result[fieldIt].empty()) + fields.erase(fieldIt); + } +} + +void CFileItemHandler::HandleFileItemList(const char *ID, bool allowFile, const char *resultname, CFileItemList &items, const CVariant ¶meterObject, CVariant &result, bool sortLimit /* = true */) +{ + HandleFileItemList(ID, allowFile, resultname, items, parameterObject, result, items.Size(), sortLimit); +} + +void CFileItemHandler::HandleFileItemList(const char *ID, bool allowFile, const char *resultname, CFileItemList &items, const CVariant ¶meterObject, CVariant &result, int size, bool sortLimit /* = true */) +{ + int start, end; + HandleLimits(parameterObject, result, size, start, end); + + if (sortLimit) + Sort(items, parameterObject); + else + { + start = 0; + end = items.Size(); + } + + CThumbLoader *thumbLoader = NULL; + if (end - start > 0) + { + if (items.Get(start)->HasVideoInfoTag()) + thumbLoader = new CVideoThumbLoader(); + else if (items.Get(start)->HasMusicInfoTag()) + thumbLoader = new CMusicThumbLoader(); + + if (thumbLoader != NULL) + thumbLoader->OnLoaderStart(); + } + + std::set<std::string> fields; + if (parameterObject.isMember("properties") && parameterObject["properties"].isArray()) + { + for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array(); + field != parameterObject["properties"].end_array(); ++field) + fields.insert(field->asString()); + } + + result[resultname].reserve(static_cast<size_t>(end - start)); + for (int i = start; i < end; i++) + { + CFileItemPtr item = items.Get(i); + HandleFileItem(ID, allowFile, resultname, item, parameterObject, fields, result, true, thumbLoader); + } + + delete thumbLoader; +} + +void CFileItemHandler::HandleFileItem(const char* ID, + bool allowFile, + const char* resultname, + const std::shared_ptr<CFileItem>& item, + const CVariant& parameterObject, + const CVariant& validFields, + CVariant& result, + bool append /* = true */, + CThumbLoader* thumbLoader /* = NULL */) +{ + std::set<std::string> fields; + if (parameterObject.isMember("properties") && parameterObject["properties"].isArray()) + { + for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array(); + field != parameterObject["properties"].end_array(); ++field) + fields.insert(field->asString()); + } + + HandleFileItem(ID, allowFile, resultname, item, parameterObject, fields, result, append, thumbLoader); +} + +void CFileItemHandler::HandleFileItem(const char* ID, + bool allowFile, + const char* resultname, + const std::shared_ptr<CFileItem>& item, + const CVariant& parameterObject, + const std::set<std::string>& validFields, + CVariant& result, + bool append /* = true */, + CThumbLoader* thumbLoader /* = NULL */) +{ + CVariant object; + std::set<std::string> fields(validFields.begin(), validFields.end()); + + if (item.get()) + { + std::set<std::string>::const_iterator fileField = fields.find("file"); + if (fileField != fields.end()) + { + if (allowFile) + { + if (item->HasVideoInfoTag() && !item->GetVideoInfoTag()->GetPath().empty()) + object["file"] = item->GetVideoInfoTag()->GetPath().c_str(); + if (item->HasMusicInfoTag() && !item->GetMusicInfoTag()->GetURL().empty()) + object["file"] = item->GetMusicInfoTag()->GetURL().c_str(); + if (item->HasPVRTimerInfoTag() && !item->GetPVRTimerInfoTag()->Path().empty()) + object["file"] = item->GetPVRTimerInfoTag()->Path().c_str(); + + if (!object.isMember("file")) + object["file"] = item->GetDynPath().c_str(); + } + fields.erase(fileField); + } + + fileField = fields.find("mediapath"); + if (fileField != fields.end()) + { + object["mediapath"] = item->GetPath().c_str(); + fields.erase(fileField); + } + + fileField = fields.find("dynpath"); + if (fileField != fields.end()) + { + object["dynpath"] = item->GetDynPath().c_str(); + fields.erase(fileField); + } + + if (ID) + { + if (item->HasPVRChannelInfoTag() && item->GetPVRChannelInfoTag()->ChannelID() > 0) + object[ID] = item->GetPVRChannelInfoTag()->ChannelID(); + else if (item->HasEPGInfoTag() && item->GetEPGInfoTag()->DatabaseID() > 0) + object[ID] = item->GetEPGInfoTag()->DatabaseID(); + else if (item->HasPVRRecordingInfoTag() && item->GetPVRRecordingInfoTag()->RecordingID() > 0) + object[ID] = item->GetPVRRecordingInfoTag()->RecordingID(); + else if (item->HasPVRTimerInfoTag() && item->GetPVRTimerInfoTag()->TimerID() > 0) + object[ID] = item->GetPVRTimerInfoTag()->TimerID(); + else if (item->HasMusicInfoTag() && item->GetMusicInfoTag()->GetDatabaseId() > 0) + object[ID] = item->GetMusicInfoTag()->GetDatabaseId(); + else if (item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_iDbId > 0) + object[ID] = item->GetVideoInfoTag()->m_iDbId; + + if (StringUtils::CompareNoCase(ID, "id") == 0) + { + if (item->HasPVRChannelInfoTag()) + object["type"] = "channel"; + else if (item->HasPVRRecordingInfoTag()) + object["type"] = "recording"; + else if (item->HasMusicInfoTag()) + { + std::string type = item->GetMusicInfoTag()->GetType(); + if (type == MediaTypeAlbum || type == MediaTypeSong || type == MediaTypeArtist) + object["type"] = type; + else if (!item->m_bIsFolder) + object["type"] = MediaTypeSong; + } + else if (item->HasVideoInfoTag() && !item->GetVideoInfoTag()->m_type.empty()) + { + std::string type = item->GetVideoInfoTag()->m_type; + if (type == MediaTypeMovie || type == MediaTypeTvShow || type == MediaTypeEpisode || type == MediaTypeMusicVideo) + object["type"] = type; + } + else if (item->HasPictureInfoTag()) + object["type"] = "picture"; + + if (!object.isMember("type")) + object["type"] = "unknown"; + + if (fields.find("filetype") != fields.end()) + { + if (item->m_bIsFolder) + object["filetype"] = "directory"; + else + object["filetype"] = "file"; + } + } + } + + bool deleteThumbloader = false; + if (thumbLoader == NULL) + { + if (item->HasVideoInfoTag()) + thumbLoader = new CVideoThumbLoader(); + else if (item->HasMusicInfoTag()) + thumbLoader = new CMusicThumbLoader(); + + if (thumbLoader != NULL) + { + deleteThumbloader = true; + thumbLoader->OnLoaderStart(); + } + } + + if (item->HasPVRChannelInfoTag()) + FillDetails(item->GetPVRChannelInfoTag().get(), item, fields, object, thumbLoader); + if (item->HasPVRChannelGroupMemberInfoTag()) + FillDetails(item->GetPVRChannelGroupMemberInfoTag().get(), item, fields, object, thumbLoader); + if (item->HasEPGInfoTag()) + FillDetails(item->GetEPGInfoTag().get(), item, fields, object, thumbLoader); + if (item->HasPVRRecordingInfoTag()) + FillDetails(item->GetPVRRecordingInfoTag().get(), item, fields, object, thumbLoader); + if (item->HasPVRTimerInfoTag()) + FillDetails(item->GetPVRTimerInfoTag().get(), item, fields, object, thumbLoader); + if (item->HasVideoInfoTag()) + FillDetails(item->GetVideoInfoTag(), item, fields, object, thumbLoader); + if (item->HasMusicInfoTag()) + FillDetails(item->GetMusicInfoTag(), item, fields, object, thumbLoader); + if (item->HasPictureInfoTag()) + FillDetails(item->GetPictureInfoTag(), item, fields, object, thumbLoader); + + FillDetails(item.get(), item, fields, object, thumbLoader); + + if (deleteThumbloader) + delete thumbLoader; + + object["label"] = item->GetLabel().c_str(); + } + else + object = CVariant(CVariant::VariantTypeNull); + + if (resultname) + { + if (append) + result[resultname].append(object); + else + result[resultname] = object; + } +} + +bool CFileItemHandler::FillFileItemList(const CVariant ¶meterObject, CFileItemList &list) +{ + CAudioLibrary::FillFileItemList(parameterObject, list); + CVideoLibrary::FillFileItemList(parameterObject, list); + CFileOperations::FillFileItemList(parameterObject, list); + + std::string file = parameterObject["file"].asString(); + if (!file.empty() && + (URIUtils::IsURL(file) || (CFileUtils::Exists(file) && !CDirectory::Exists(file)))) + { + bool added = false; + for (int index = 0; index < list.Size(); index++) + { + if (list[index]->GetDynPath() == file || + list[index]->GetMusicInfoTag()->GetURL() == file || list[index]->GetVideoInfoTag()->GetPath() == file) + { + added = true; + break; + } + } + + if (!added) + { + CFileItemPtr item = CFileItemPtr(new CFileItem(file, false)); + if (item->IsPicture()) + { + CPictureInfoTag picture; + picture.Load(item->GetPath()); + *item->GetPictureInfoTag() = picture; + } + if (item->GetLabel().empty()) + { + item->SetLabel(CUtil::GetTitleFromPath(file, false)); + if (item->GetLabel().empty()) + item->SetLabel(URIUtils::GetFileName(file)); + } + list.Add(item); + } + } + + return (list.Size() > 0); +} + +void CFileItemHandler::Sort(CFileItemList &items, const CVariant ¶meterObject) +{ + SortDescription sorting; + if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes)) + return; + + items.Sort(sorting); +} diff --git a/xbmc/interfaces/json-rpc/FileItemHandler.h b/xbmc/interfaces/json-rpc/FileItemHandler.h new file mode 100644 index 0000000..ba7e636 --- /dev/null +++ b/xbmc/interfaces/json-rpc/FileItemHandler.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPC.h" +#include "JSONUtils.h" + +#include <memory> +#include <set> + +class CFileItem; +class CFileItemList; +class CThumbLoader; +class CVariant; +class ISerializable; + +namespace JSONRPC +{ + class CFileItemHandler : public CJSONUtils + { + protected: + static void FillDetails(const ISerializable* info, + const std::shared_ptr<CFileItem>& item, + std::set<std::string>& fields, + CVariant& result, + CThumbLoader* thumbLoader = nullptr); + static void HandleFileItemList(const char *ID, bool allowFile, const char *resultname, CFileItemList &items, const CVariant ¶meterObject, CVariant &result, bool sortLimit = true); + static void HandleFileItemList(const char *ID, bool allowFile, const char *resultname, CFileItemList &items, const CVariant ¶meterObject, CVariant &result, int size, bool sortLimit = true); + static void HandleFileItem(const char* ID, + bool allowFile, + const char* resultname, + const std::shared_ptr<CFileItem>& item, + const CVariant& parameterObject, + const CVariant& validFields, + CVariant& result, + bool append = true, + CThumbLoader* thumbLoader = nullptr); + static void HandleFileItem(const char* ID, + bool allowFile, + const char* resultname, + const std::shared_ptr<CFileItem>& item, + const CVariant& parameterObject, + const std::set<std::string>& validFields, + CVariant& result, + bool append = true, + CThumbLoader* thumbLoader = nullptr); + + static bool FillFileItemList(const CVariant ¶meterObject, CFileItemList &list); + private: + static void Sort(CFileItemList &items, const CVariant& parameterObject); + static bool GetField(const std::string& field, + const CVariant& info, + const std::shared_ptr<CFileItem>& item, + CVariant& result, + bool& fetchedArt, + CThumbLoader* thumbLoader = nullptr); + }; +} diff --git a/xbmc/interfaces/json-rpc/FileOperations.cpp b/xbmc/interfaces/json-rpc/FileOperations.cpp new file mode 100644 index 0000000..6329a78 --- /dev/null +++ b/xbmc/interfaces/json-rpc/FileOperations.cpp @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "FileOperations.h" + +#include "AudioLibrary.h" +#include "FileItem.h" +#include "MediaSource.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "Util.h" +#include "VideoLibrary.h" +#include "filesystem/Directory.h" +#include "media/MediaLockState.h" +#include "settings/AdvancedSettings.h" +#include "settings/MediaSourceSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/FileExtensionProvider.h" +#include "utils/FileUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "video/VideoDatabase.h" + +using namespace XFILE; +using namespace JSONRPC; + +JSONRPC_STATUS CFileOperations::GetRootDirectory(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string media = parameterObject["media"].asString(); + StringUtils::ToLower(media); + + VECSOURCES *sources = CMediaSourceSettings::GetInstance().GetSources(media); + if (sources) + { + CFileItemList items; + for (unsigned int i = 0; i < (unsigned int)sources->size(); i++) + { + // Do not show sources which are locked + if (sources->at(i).m_iHasLock == LOCK_STATE_LOCKED) + continue; + + items.Add(CFileItemPtr(new CFileItem(sources->at(i)))); + } + + for (unsigned int i = 0; i < (unsigned int)items.Size(); i++) + { + if (items[i]->IsSmb()) + { + CURL url(items[i]->GetPath()); + items[i]->SetPath(url.GetWithoutUserDetails()); + } + } + + CVariant param = parameterObject; + param["properties"] = CVariant(CVariant::VariantTypeArray); + param["properties"].append("file"); + + HandleFileItemList(NULL, true, "sources", items, param, result); + } + + return OK; +} + +JSONRPC_STATUS CFileOperations::GetDirectory(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string media = parameterObject["media"].asString(); + StringUtils::ToLower(media); + + CFileItemList items; + std::string strPath = parameterObject["directory"].asString(); + + if (!CFileUtils::RemoteAccessAllowed(strPath)) + return InvalidParams; + + std::vector<std::string> regexps; + std::string extensions; + if (media == "video") + { + regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoExcludeFromListingRegExps; + extensions = CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(); + items.SetProperty("set_videodb_details", + CVideoLibrary::GetDetailsFromJsonParameters(parameterObject)); + } + else if (media == "music") + { + regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_audioExcludeFromListingRegExps; + extensions = CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(); + } + else if (media == "pictures") + { + regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_pictureExcludeFromListingRegExps; + extensions = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(); + } + + if (CDirectory::GetDirectory(strPath, items, extensions, DIR_FLAG_DEFAULTS)) + { + // we might need to get additional information for music items + if (media == "music") + { + JSONRPC_STATUS status = CAudioLibrary::GetAdditionalDetails(parameterObject, items); + if (status != OK) + return status; + } + + CFileItemList filteredFiles; + for (unsigned int i = 0; i < (unsigned int)items.Size(); i++) + { + if (CUtil::ExcludeFileOrFolder(items[i]->GetPath(), regexps)) + continue; + + if (items[i]->IsSmb()) + { + CURL url(items[i]->GetPath()); + items[i]->SetPath(url.GetWithoutUserDetails()); + } + + if ((media == "video" && items[i]->HasVideoInfoTag()) || + (media == "music" && items[i]->HasMusicInfoTag()) || + (media == "picture" && items[i]->HasPictureInfoTag()) || + media == "files" || + URIUtils::IsUPnP(items.GetPath())) + filteredFiles.Add(items[i]); + else + { + CFileItemPtr fileItem(new CFileItem()); + if (FillFileItem(items[i], fileItem, media, parameterObject)) + filteredFiles.Add(fileItem); + else + filteredFiles.Add(items[i]); + } + } + + // Check if the "properties" list exists + // and make sure it contains the "file" and "filetype" + // fields + CVariant param = parameterObject; + if (!param.isMember("properties")) + param["properties"] = CVariant(CVariant::VariantTypeArray); + + bool hasFileField = false; + for (CVariant::const_iterator_array itr = param["properties"].begin_array(); + itr != param["properties"].end_array(); ++itr) + { + if (itr->asString().compare("file") == 0) + { + hasFileField = true; + break; + } + } + + if (!hasFileField) + param["properties"].append("file"); + param["properties"].append("filetype"); + + HandleFileItemList("id", true, "files", filteredFiles, param, result); + + return OK; + } + + return InvalidParams; +} + +JSONRPC_STATUS CFileOperations::GetFileDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string file = parameterObject["file"].asString(); + if (!CFileUtils::Exists(file)) + return InvalidParams; + + if (!CFileUtils::RemoteAccessAllowed(file)) + return InvalidParams; + + std::string path = URIUtils::GetDirectory(file); + + CFileItemList items; + if (path.empty()) + return InvalidParams; + + CFileItemPtr item; + if (CDirectory::GetDirectory(path, items, "", DIR_FLAG_DEFAULTS) && items.Contains(file)) + item = items.Get(file); + else + item = CFileItemPtr(new CFileItem(file, false)); + + if (!URIUtils::IsUPnP(file)) + FillFileItem(item, item, parameterObject["media"].asString(), parameterObject); + + // Check if the "properties" list exists + // and make sure it contains the "file" + // field + CVariant param = parameterObject; + if (!param.isMember("properties")) + param["properties"] = CVariant(CVariant::VariantTypeArray); + + bool hasFileField = false; + for (CVariant::const_iterator_array itr = param["properties"].begin_array(); + itr != param["properties"].end_array(); ++itr) + { + if (itr->asString().compare("file") == 0) + { + hasFileField = true; + break; + } + } + + if (!hasFileField) + param["properties"].append("file"); + param["properties"].append("filetype"); + + HandleFileItem("id", true, "filedetails", item, parameterObject, param["properties"], result, false); + return OK; +} + +JSONRPC_STATUS CFileOperations::SetFileDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string media = parameterObject["media"].asString(); + StringUtils::ToLower(media); + + if (media.compare("video") != 0) + return InvalidParams; + + std::string file = parameterObject["file"].asString(); + if (!CFileUtils::Exists(file)) + return InvalidParams; + + if (!CFileUtils::RemoteAccessAllowed(file)) + return InvalidParams; + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + int fileId = videodatabase.AddFile(file); + + CVideoInfoTag infos; + if (!videodatabase.GetFileInfo("", infos, fileId)) + return InvalidParams; + + CDateTime lastPlayed = infos.m_lastPlayed; + int playcount = infos.GetPlayCount(); + if (!parameterObject["lastplayed"].isNull()) + { + lastPlayed.Reset(); + SetFromDBDateTime(parameterObject["lastplayed"], lastPlayed); + playcount = lastPlayed.IsValid() ? std::max(1, playcount) : 0; + } + if (!parameterObject["playcount"].isNull()) + playcount = parameterObject["playcount"].asInteger(); + if (playcount != infos.GetPlayCount() || lastPlayed != infos.m_lastPlayed) + videodatabase.SetPlayCount(CFileItem(infos), playcount, lastPlayed); + + CVideoLibrary::UpdateResumePoint(parameterObject, infos, videodatabase); + + videodatabase.GetFileInfo("", infos, fileId); + CJSONRPCUtils::NotifyItemUpdated(infos, std::map<std::string, std::string>{}); + return ACK; +} + +JSONRPC_STATUS CFileOperations::PrepareDownload(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string protocol; + if (transport->PrepareDownload(parameterObject["path"].asString().c_str(), result["details"], protocol)) + { + result["protocol"] = protocol; + + if ((transport->GetCapabilities() & FileDownloadDirect) == FileDownloadDirect) + result["mode"] = "direct"; + else + result["mode"] = "redirect"; + + return OK; + } + + return InvalidParams; +} + +JSONRPC_STATUS CFileOperations::Download(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return transport->Download(parameterObject["path"].asString().c_str(), result) ? OK : InvalidParams; +} + +bool CFileOperations::FillFileItem( + const std::shared_ptr<CFileItem>& originalItem, + std::shared_ptr<CFileItem>& item, + const std::string& media /* = "" */, + const CVariant& parameterObject /* = CVariant(CVariant::VariantTypeArray) */) +{ + if (originalItem.get() == NULL) + return false; + + // copy all the available details + *item = *originalItem; + + bool status = false; + std::string strFilename = originalItem->GetPath(); + if (!strFilename.empty() && (CDirectory::Exists(strFilename) || CFileUtils::Exists(strFilename))) + { + if (media == "video") + status = CVideoLibrary::FillFileItem(strFilename, item, parameterObject); + else if (media == "music") + status = CAudioLibrary::FillFileItem(strFilename, item, parameterObject); + + if (status && item->GetLabel().empty()) + { + std::string label = originalItem->GetLabel(); + if (label.empty()) + { + bool isDir = CDirectory::Exists(strFilename); + label = CUtil::GetTitleFromPath(strFilename, isDir); + if (label.empty()) + label = URIUtils::GetFileName(strFilename); + } + + item->SetLabel(label); + } + else if (!status) + { + if (originalItem->GetLabel().empty()) + { + bool isDir = CDirectory::Exists(strFilename); + std::string label = CUtil::GetTitleFromPath(strFilename, isDir); + if (label.empty()) + return false; + + item->SetLabel(label); + item->SetPath(strFilename); + item->m_bIsFolder = isDir; + } + else + *item = *originalItem; + + status = true; + } + } + + return status; +} + +bool CFileOperations::FillFileItemList(const CVariant ¶meterObject, CFileItemList &list) +{ + if (parameterObject.isMember("directory")) + { + std::string media = parameterObject["media"].asString(); + StringUtils::ToLower(media); + + std::string strPath = parameterObject["directory"].asString(); + if (!strPath.empty()) + { + CFileItemList items; + std::string extensions; + std::vector<std::string> regexps; + + if (media == "video") + { + regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoExcludeFromListingRegExps; + extensions = CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(); + } + else if (media == "music") + { + regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_audioExcludeFromListingRegExps; + extensions = CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(); + } + else if (media == "pictures") + { + regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_pictureExcludeFromListingRegExps; + extensions = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(); + } + + CDirectory directory; + if (directory.GetDirectory(strPath, items, extensions, DIR_FLAG_DEFAULTS)) + { + // Sort folders and files by filename to avoid reverse item order bug on some platforms, + // but leave items from a playlist, smartplaylist or upnp container in order supplied + if (!items.IsPlayList() && !items.IsSmartPlayList() && !URIUtils::IsUPnP(items.GetPath())) + items.Sort(SortByFile, SortOrderAscending); + + CFileItemList filteredDirectories; + for (unsigned int i = 0; i < (unsigned int)items.Size(); i++) + { + if (CUtil::ExcludeFileOrFolder(items[i]->GetPath(), regexps)) + continue; + + if (items[i]->m_bIsFolder) + filteredDirectories.Add(items[i]); + else if ((media == "video" && items[i]->HasVideoInfoTag()) || + (media == "music" && items[i]->HasMusicInfoTag())) + list.Add(items[i]); + else + { + CFileItemPtr fileItem(new CFileItem()); + if (FillFileItem(items[i], fileItem, media, parameterObject)) + list.Add(fileItem); + else if (media == "files") + list.Add(items[i]); + } + } + + if (parameterObject.isMember("recursive") && parameterObject["recursive"].isBoolean()) + { + for (int i = 0; i < filteredDirectories.Size(); i++) + { + CVariant val = parameterObject; + val["directory"] = filteredDirectories[i]->GetPath(); + FillFileItemList(val, list); + } + } + + return true; + } + } + } + + return false; +} diff --git a/xbmc/interfaces/json-rpc/FileOperations.h b/xbmc/interfaces/json-rpc/FileOperations.h new file mode 100644 index 0000000..6af05bb --- /dev/null +++ b/xbmc/interfaces/json-rpc/FileOperations.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "FileItemHandler.h" +#include "JSONRPC.h" + +#include <memory> + +class CFileItem; +class CVariant; + +namespace JSONRPC +{ + class CFileOperations : public CFileItemHandler + { + public: + static JSONRPC_STATUS GetRootDirectory(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetDirectory(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetFileDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetFileDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS PrepareDownload(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Download(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static bool FillFileItem( + const std::shared_ptr<CFileItem>& originalItem, + std::shared_ptr<CFileItem>& item, + const std::string& media = "", + const CVariant& parameterObject = CVariant(CVariant::VariantTypeArray)); + static bool FillFileItemList(const CVariant ¶meterObject, CFileItemList &list); + }; +} diff --git a/xbmc/interfaces/json-rpc/GUIOperations.cpp b/xbmc/interfaces/json-rpc/GUIOperations.cpp new file mode 100644 index 0000000..06b1f75 --- /dev/null +++ b/xbmc/interfaces/json-rpc/GUIOperations.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIOperations.h" + +#include "GUIInfoManager.h" +#include "ServiceBroker.h" +#include "addons/AddonManager.h" +#include "addons/IAddon.h" +#include "addons/addoninfo/AddonType.h" +#include "application/Application.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/StereoscopicsManager.h" +#include "input/Key.h" +#include "input/WindowTranslator.h" +#include "messaging/ApplicationMessenger.h" +#include "rendering/RenderSystem.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/Variant.h" + +using namespace JSONRPC; +using namespace ADDON; + +JSONRPC_STATUS CGUIOperations::GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVariant properties = CVariant(CVariant::VariantTypeObject); + for (unsigned int index = 0; index < parameterObject["properties"].size(); index++) + { + std::string propertyName = parameterObject["properties"][index].asString(); + CVariant property; + JSONRPC_STATUS ret; + if ((ret = GetPropertyValue(propertyName, property)) != OK) + return ret; + + properties[propertyName] = property; + } + + result = properties; + + return OK; +} + +JSONRPC_STATUS CGUIOperations::ActivateWindow(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int iWindow = CWindowTranslator::TranslateWindow(parameterObject["window"].asString()); + if (iWindow != WINDOW_INVALID) + { + std::vector<std::string> params; + for (CVariant::const_iterator_array param = parameterObject["parameters"].begin_array(); + param != parameterObject["parameters"].end_array(); ++param) + { + if (param->isString() && !param->empty()) + params.push_back(param->asString()); + } + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTIVATE_WINDOW, iWindow, 0, nullptr, "", + params); + return ACK; + } + + return InvalidParams; +} + +JSONRPC_STATUS CGUIOperations::ShowNotification(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string image = parameterObject["image"].asString(); + std::string title = parameterObject["title"].asString(); + std::string message = parameterObject["message"].asString(); + unsigned int displaytime = (unsigned int)parameterObject["displaytime"].asUnsignedInteger(); + + if (image.compare("info") == 0) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, title, message, displaytime); + else if (image.compare("warning") == 0) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Warning, title, message, displaytime); + else if (image.compare("error") == 0) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, title, message, displaytime); + else + CGUIDialogKaiToast::QueueNotification(image, title, message, displaytime); + + return ACK; +} + +JSONRPC_STATUS CGUIOperations::SetFullscreen(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if ((parameterObject["fullscreen"].isString() && + parameterObject["fullscreen"].asString().compare("toggle") == 0) || + (parameterObject["fullscreen"].isBoolean() && + parameterObject["fullscreen"].asBoolean() != g_application.IsFullScreen())) + { + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast<void*>(new CAction(ACTION_SHOW_GUI))); + } + else if (!parameterObject["fullscreen"].isBoolean() && !parameterObject["fullscreen"].isString()) + return InvalidParams; + + return GetPropertyValue("fullscreen", result); +} + +JSONRPC_STATUS CGUIOperations::SetStereoscopicMode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CAction action = CStereoscopicsManager::ConvertActionCommandToAction("SetStereoMode", parameterObject["mode"].asString()); + if (action.GetID() != ACTION_NONE) + { + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast<void*>(new CAction(action))); + return ACK; + } + + return InvalidParams; +} + +JSONRPC_STATUS CGUIOperations::GetStereoscopicModes(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + for (int i = RENDER_STEREO_MODE_OFF; i < RENDER_STEREO_MODE_COUNT; i++) + { + RENDER_STEREO_MODE mode = (RENDER_STEREO_MODE) i; + if (CServiceBroker::GetRenderSystem()->SupportsStereo(mode)) + result["stereoscopicmodes"].push_back(GetStereoModeObjectFromGuiMode(mode)); + } + + return OK; +} + +JSONRPC_STATUS CGUIOperations::GetPropertyValue(const std::string &property, CVariant &result) +{ + if (property == "currentwindow") + { + result["label"] = CServiceBroker::GetGUI()->GetInfoManager().GetLabel( + CServiceBroker::GetGUI()->GetInfoManager().TranslateString("System.CurrentWindow"), + INFO::DEFAULT_CONTEXT); + result["id"] = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog(); + } + else if (property == "currentcontrol") + result["label"] = CServiceBroker::GetGUI()->GetInfoManager().GetLabel( + CServiceBroker::GetGUI()->GetInfoManager().TranslateString("System.CurrentControl"), + INFO::DEFAULT_CONTEXT); + else if (property == "skin") + { + std::string skinId = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOOKANDFEEL_SKIN); + AddonPtr addon; + if (!CServiceBroker::GetAddonMgr().GetAddon(skinId, addon, AddonType::SKIN, + OnlyEnabled::CHOICE_YES)) + return InternalError; + + result["id"] = skinId; + if (addon.get()) + result["name"] = addon->Name(); + } + else if (property == "fullscreen") + result = g_application.IsFullScreen(); + else if (property == "stereoscopicmode") + { + const CStereoscopicsManager &stereoscopicsManager = CServiceBroker::GetGUI()->GetStereoscopicsManager(); + + result = GetStereoModeObjectFromGuiMode(stereoscopicsManager.GetStereoMode()); + } + else + return InvalidParams; + + return OK; +} + +CVariant CGUIOperations::GetStereoModeObjectFromGuiMode(const RENDER_STEREO_MODE &mode) +{ + const CStereoscopicsManager &stereoscopicsManager = CServiceBroker::GetGUI()->GetStereoscopicsManager(); + + CVariant modeObj(CVariant::VariantTypeObject); + modeObj["mode"] = stereoscopicsManager.ConvertGuiStereoModeToString(mode); + modeObj["label"] = stereoscopicsManager.GetLabelForStereoMode(mode); + return modeObj; +} diff --git a/xbmc/interfaces/json-rpc/GUIOperations.h b/xbmc/interfaces/json-rpc/GUIOperations.h new file mode 100644 index 0000000..04c3f81 --- /dev/null +++ b/xbmc/interfaces/json-rpc/GUIOperations.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPC.h" +#include "rendering/RenderSystemTypes.h" + +class CVariant; + +namespace JSONRPC +{ + class CGUIOperations + { + public: + static JSONRPC_STATUS GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS ActivateWindow(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS ShowNotification(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetFullscreen(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetStereoscopicMode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetStereoscopicModes(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + private: + static JSONRPC_STATUS GetPropertyValue(const std::string &property, CVariant &result); + static CVariant GetStereoModeObjectFromGuiMode(const RENDER_STEREO_MODE &mode); + }; +} diff --git a/xbmc/interfaces/json-rpc/IClient.h b/xbmc/interfaces/json-rpc/IClient.h new file mode 100644 index 0000000..18a14df --- /dev/null +++ b/xbmc/interfaces/json-rpc/IClient.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +namespace JSONRPC +{ + class IClient + { + public: + virtual ~IClient() = default; + virtual int GetPermissionFlags() = 0; + virtual int GetAnnouncementFlags() = 0; + virtual bool SetAnnouncementFlags(int flags) = 0; + }; +} diff --git a/xbmc/interfaces/json-rpc/IJSONRPCAnnouncer.h b/xbmc/interfaces/json-rpc/IJSONRPCAnnouncer.h new file mode 100644 index 0000000..af7d2e8 --- /dev/null +++ b/xbmc/interfaces/json-rpc/IJSONRPCAnnouncer.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "interfaces/IAnnouncer.h" +#include "utils/JSONVariantWriter.h" +#include "utils/Variant.h" + +namespace JSONRPC +{ + class IJSONRPCAnnouncer : public ANNOUNCEMENT::IAnnouncer + { + public: + ~IJSONRPCAnnouncer() override = default; + + protected: + static std::string AnnouncementToJSONRPC(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& method, + const CVariant& data, + bool compactOutput) + { + CVariant root; + root["jsonrpc"] = "2.0"; + + std::string namespaceMethod = ANNOUNCEMENT::AnnouncementFlagToString(flag); + namespaceMethod += "."; + namespaceMethod += method; + root["method"] = namespaceMethod; + + root["params"]["data"] = data; + root["params"]["sender"] = sender; + + std::string str; + CJSONVariantWriter::Write(root, str, compactOutput); + + return str; + } + }; +} diff --git a/xbmc/interfaces/json-rpc/ITransportLayer.h b/xbmc/interfaces/json-rpc/ITransportLayer.h new file mode 100644 index 0000000..11a0c78 --- /dev/null +++ b/xbmc/interfaces/json-rpc/ITransportLayer.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <string> + +class CVariant; + +namespace JSONRPC +{ + enum TransportLayerCapability + { + Response = 0x1, + Announcing = 0x2, + FileDownloadRedirect = 0x4, + FileDownloadDirect = 0x8 + }; + + #define TRANSPORT_LAYER_CAPABILITY_ALL (Response | Announcing | FileDownloadRedirect | FileDownloadDirect) + + class ITransportLayer + { + public: + virtual ~ITransportLayer() = default; + virtual bool PrepareDownload(const char *path, CVariant &details, std::string &protocol) = 0; + virtual bool Download(const char *path, CVariant &result) = 0; + virtual int GetCapabilities() = 0; + }; +} diff --git a/xbmc/interfaces/json-rpc/InputOperations.cpp b/xbmc/interfaces/json-rpc/InputOperations.cpp new file mode 100644 index 0000000..0b637fb --- /dev/null +++ b/xbmc/interfaces/json-rpc/InputOperations.cpp @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "InputOperations.h" + +#include "ServiceBroker.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPowerHandling.h" +#include "guilib/GUIAudioManager.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIWindow.h" +#include "guilib/GUIWindowManager.h" +#include "input/ButtonTranslator.h" +#include "input/actions/ActionIDs.h" +#include "input/actions/ActionTranslator.h" +#include "messaging/ApplicationMessenger.h" +#include "utils/Variant.h" + +using namespace JSONRPC; + +//! @todo the breakage of the screensaver should be refactored +//! to one central super duper place for getting rid of +//! 1 million dupes +bool CInputOperations::handleScreenSaver() +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->ResetScreenSaver(); + return appPower->WakeUpScreenSaverAndDPMS(); +} + +JSONRPC_STATUS CInputOperations::SendAction(int actionID, bool wakeScreensaver /* = true */, bool waitResult /* = false */) +{ + if (!wakeScreensaver || !handleScreenSaver()) + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->ResetSystemIdleTimer(); + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (gui) + gui->GetAudioManager().PlayActionSound(actionID); + + if (waitResult) + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast<void*>(new CAction(actionID))); + else + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast<void*>(new CAction(actionID))); + } + return ACK; +} + +JSONRPC_STATUS CInputOperations::activateWindow(int windowID) +{ + if(!handleScreenSaver()) + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTIVATE_WINDOW, windowID, 0); + + return ACK; +} + +JSONRPC_STATUS CInputOperations::SendText(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (CGUIKeyboardFactory::SendTextToActiveKeyboard(parameterObject["text"].asString(), parameterObject["done"].asBoolean())) + return ACK; + + CGUIWindow *window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog()); + if (!window) + return ACK; + + CGUIMessage msg(GUI_MSG_SET_TEXT, 0, window->GetFocusedControlID()); + msg.SetLabel(parameterObject["text"].asString()); + msg.SetParam1(parameterObject["done"].asBoolean() ? 1 : 0); + CServiceBroker::GetAppMessenger()->SendGUIMessage(msg, window->GetID()); + + return ACK; +} + +JSONRPC_STATUS CInputOperations::ExecuteAction(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + unsigned int action; + if (!CActionTranslator::TranslateString(parameterObject["action"].asString(), action)) + return InvalidParams; + + return SendAction(action); +} + +JSONRPC_STATUS CInputOperations::ButtonEvent(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) +{ + std::string button = parameterObject["button"].asString(); + std::string keymap = parameterObject["keymap"].asString(); + int holdtime = static_cast<int>(parameterObject["holdtime"].asInteger()); + if (holdtime < 0) + { + return InvalidParams; + } + + uint32_t keycode = CButtonTranslator::TranslateString(keymap, button); + if (keycode == 0) + { + return InvalidParams; + } + + XBMC_Event* newEvent = new XBMC_Event; + newEvent->type = XBMC_BUTTON; + newEvent->keybutton.button = keycode; + newEvent->keybutton.holdtime = holdtime; + + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EVENT, -1, -1, static_cast<void*>(newEvent)); + + return ACK; +} + +JSONRPC_STATUS CInputOperations::Left(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_MOVE_LEFT); +} + +JSONRPC_STATUS CInputOperations::Right(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_MOVE_RIGHT); +} + +JSONRPC_STATUS CInputOperations::Down(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_MOVE_DOWN); +} + +JSONRPC_STATUS CInputOperations::Up(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_MOVE_UP); +} + +JSONRPC_STATUS CInputOperations::Select(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_SELECT_ITEM); +} + +JSONRPC_STATUS CInputOperations::Back(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_NAV_BACK); +} + +JSONRPC_STATUS CInputOperations::ContextMenu(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_CONTEXT_MENU); +} + +JSONRPC_STATUS CInputOperations::Info(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_SHOW_INFO); +} + +JSONRPC_STATUS CInputOperations::Home(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return activateWindow(WINDOW_HOME); +} + +JSONRPC_STATUS CInputOperations::ShowCodec(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return MethodNotFound; +} + +JSONRPC_STATUS CInputOperations::ShowOSD(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_SHOW_OSD); +} + +JSONRPC_STATUS CInputOperations::ShowPlayerProcessInfo(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_PLAYER_PROCESS_INFO); +} diff --git a/xbmc/interfaces/json-rpc/InputOperations.h b/xbmc/interfaces/json-rpc/InputOperations.h new file mode 100644 index 0000000..a431a1c --- /dev/null +++ b/xbmc/interfaces/json-rpc/InputOperations.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPC.h" + +class CVariant; + +namespace JSONRPC +{ + class CInputOperations + { + public: + static JSONRPC_STATUS SendText(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS ExecuteAction(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS ButtonEvent(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result); + + static JSONRPC_STATUS Left(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Right(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Down(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Up(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS Select(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Back(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS ContextMenu(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Info(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Home(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS ShowCodec(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS ShowOSD(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS ShowPlayerProcessInfo(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS SendAction(int actionID, bool wakeScreensaver = true, bool waitResult = false); + + private: + static JSONRPC_STATUS activateWindow(int windowID); + static bool handleScreenSaver(); + }; +} diff --git a/xbmc/interfaces/json-rpc/JSONRPC.cpp b/xbmc/interfaces/json-rpc/JSONRPC.cpp new file mode 100644 index 0000000..4b86188 --- /dev/null +++ b/xbmc/interfaces/json-rpc/JSONRPC.cpp @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "JSONRPC.h" + +#include "FileItem.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "ServiceDescription.h" +#include "TextureDatabase.h" +#include "addons/Addon.h" +#include "addons/IAddon.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "dbwrappers/DatabaseQuery.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIMessage.h" +#include "guilib/GUIWindowManager.h" +#include "input/WindowTranslator.h" +#include "input/actions/ActionTranslator.h" +#include "interfaces/AnnouncementManager.h" +#include "playlists/SmartPlayList.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" + +#include <string.h> + +using namespace JSONRPC; + +bool CJSONRPC::m_initialized = false; + +void CJSONRPC::Initialize() +{ + if (m_initialized) + return; + + // Add some types/enums at runtime + std::vector<std::string> enumList; + for (int addonType = static_cast<int>(ADDON::AddonType::UNKNOWN); + addonType < static_cast<int>(ADDON::AddonType::MAX_TYPES); addonType++) + enumList.push_back( + ADDON::CAddonInfo::TranslateType(static_cast<ADDON::AddonType>(addonType), false)); + CJSONServiceDescription::AddEnum("Addon.Types", enumList); + + enumList.clear(); + CActionTranslator::GetActions(enumList); + CJSONServiceDescription::AddEnum("Input.Action", enumList); + + enumList.clear(); + CWindowTranslator::GetWindows(enumList); + CJSONServiceDescription::AddEnum("GUI.Window", enumList); + + // filter-related enums + std::vector<std::string> smartplaylistList; + CDatabaseQueryRule::GetAvailableOperators(smartplaylistList); + CJSONServiceDescription::AddEnum("List.Filter.Operators", smartplaylistList); + + smartplaylistList.clear(); + CSmartPlaylist::GetAvailableFields("movies", smartplaylistList); + CJSONServiceDescription::AddEnum("List.Filter.Fields.Movies", smartplaylistList); + + smartplaylistList.clear(); + CSmartPlaylist::GetAvailableFields("tvshows", smartplaylistList); + CJSONServiceDescription::AddEnum("List.Filter.Fields.TVShows", smartplaylistList); + + smartplaylistList.clear(); + CSmartPlaylist::GetAvailableFields("episodes", smartplaylistList); + CJSONServiceDescription::AddEnum("List.Filter.Fields.Episodes", smartplaylistList); + + smartplaylistList.clear(); + CSmartPlaylist::GetAvailableFields("musicvideos", smartplaylistList); + CJSONServiceDescription::AddEnum("List.Filter.Fields.MusicVideos", smartplaylistList); + + smartplaylistList.clear(); + CSmartPlaylist::GetAvailableFields("artists", smartplaylistList); + CJSONServiceDescription::AddEnum("List.Filter.Fields.Artists", smartplaylistList); + + smartplaylistList.clear(); + CSmartPlaylist::GetAvailableFields("albums", smartplaylistList); + CJSONServiceDescription::AddEnum("List.Filter.Fields.Albums", smartplaylistList); + + smartplaylistList.clear(); + CSmartPlaylist::GetAvailableFields("songs", smartplaylistList); + CJSONServiceDescription::AddEnum("List.Filter.Fields.Songs", smartplaylistList); + + smartplaylistList.clear(); + CTextureRule::GetAvailableFields(smartplaylistList); + CJSONServiceDescription::AddEnum("List.Filter.Fields.Textures", smartplaylistList); + + unsigned int size = sizeof(JSONRPC_SERVICE_TYPES) / sizeof(char*); + + for (unsigned int index = 0; index < size; index++) + CJSONServiceDescription::AddType(JSONRPC_SERVICE_TYPES[index]); + + size = sizeof(JSONRPC_SERVICE_METHODS) / sizeof(char*); + + for (unsigned int index = 0; index < size; index++) + CJSONServiceDescription::AddBuiltinMethod(JSONRPC_SERVICE_METHODS[index]); + + size = sizeof(JSONRPC_SERVICE_NOTIFICATIONS) / sizeof(char*); + + for (unsigned int index = 0; index < size; index++) + CJSONServiceDescription::AddNotification(JSONRPC_SERVICE_NOTIFICATIONS[index]); + + CJSONServiceDescription::ResolveReferences(); + + m_initialized = true; + CLog::Log(LOGINFO, "JSONRPC v{}: Successfully initialized", + CJSONServiceDescription::GetVersion()); +} + +void CJSONRPC::Cleanup() +{ + CJSONServiceDescription::Cleanup(); + m_initialized = false; +} + +JSONRPC_STATUS CJSONRPC::Introspect(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result) +{ + return CJSONServiceDescription::Print(result, transport, client, + parameterObject["getdescriptions"].asBoolean(), parameterObject["getmetadata"].asBoolean(), parameterObject["filterbytransport"].asBoolean(), + parameterObject["filter"]["id"].asString(), parameterObject["filter"]["type"].asString(), parameterObject["filter"]["getreferences"].asBoolean()); +} + +JSONRPC_STATUS CJSONRPC::Version(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result) +{ + result["version"]["major"] = 0; + result["version"]["minor"] = 0; + result["version"]["patch"] = 0; + + const char* version = CJSONServiceDescription::GetVersion(); + if (version != NULL) + { + std::vector<std::string> parts = StringUtils::Split(version, "."); + if (!parts.empty()) + result["version"]["major"] = (int)strtol(parts[0].c_str(), NULL, 10); + if (parts.size() > 1) + result["version"]["minor"] = (int)strtol(parts[1].c_str(), NULL, 10); + if (parts.size() > 2) + result["version"]["patch"] = (int)strtol(parts[2].c_str(), NULL, 10); + } + + return OK; +} + +JSONRPC_STATUS CJSONRPC::Permission(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result) +{ + int flags = client->GetPermissionFlags(); + + for (int i = 1; i <= OPERATION_PERMISSION_ALL; i *= 2) + result[PermissionToString((OperationPermission)i)] = (flags & i) == i; + + return OK; +} + +JSONRPC_STATUS CJSONRPC::Ping(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result) +{ + CVariant temp = "pong"; + result.swap(temp); + return OK; +} + +JSONRPC_STATUS CJSONRPC::GetConfiguration(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result) +{ + int flags = client->GetAnnouncementFlags(); + + for (int i = 1; i <= ANNOUNCEMENT::ANNOUNCE_ALL; i *= 2) + result["notifications"][AnnouncementFlagToString((ANNOUNCEMENT::AnnouncementFlag)i)] = (flags & i) == i; + + return OK; +} + +JSONRPC_STATUS CJSONRPC::SetConfiguration(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result) +{ + int flags = 0; + int oldFlags = client->GetAnnouncementFlags(); + + if (parameterObject.isMember("notifications")) + { + CVariant notifications = parameterObject["notifications"]; + if ((notifications["Player"].isNull() && (oldFlags & ANNOUNCEMENT::Player)) || + (notifications["Player"].isBoolean() && notifications["Player"].asBoolean())) + flags |= ANNOUNCEMENT::Player; + if ((notifications["Playlist"].isNull() && (oldFlags & ANNOUNCEMENT::Playlist)) || + (notifications["Playlist"].isBoolean() && notifications["Playlist"].asBoolean())) + flags |= ANNOUNCEMENT::Playlist; + if ((notifications["GUI"].isNull() && (oldFlags & ANNOUNCEMENT::GUI)) || + (notifications["GUI"].isBoolean() && notifications["GUI"].asBoolean())) + flags |= ANNOUNCEMENT::GUI; + if ((notifications["System"].isNull() && (oldFlags & ANNOUNCEMENT::System)) || + (notifications["System"].isBoolean() && notifications["System"].asBoolean())) + flags |= ANNOUNCEMENT::System; + if ((notifications["VideoLibrary"].isNull() && (oldFlags & ANNOUNCEMENT::VideoLibrary)) || + (notifications["VideoLibrary"].isBoolean() && notifications["VideoLibrary"].asBoolean())) + flags |= ANNOUNCEMENT::VideoLibrary; + if ((notifications["AudioLibrary"].isNull() && (oldFlags & ANNOUNCEMENT::AudioLibrary)) || + (notifications["AudioLibrary"].isBoolean() && notifications["AudioLibrary"].asBoolean())) + flags |= ANNOUNCEMENT::AudioLibrary; + if ((notifications["Application"].isNull() && (oldFlags & ANNOUNCEMENT::Other)) || + (notifications["Application"].isBoolean() && notifications["Application"].asBoolean())) + flags |= ANNOUNCEMENT::Application; + if ((notifications["Input"].isNull() && (oldFlags & ANNOUNCEMENT::Input)) || + (notifications["Input"].isBoolean() && notifications["Input"].asBoolean())) + flags |= ANNOUNCEMENT::Input; + if ((notifications["Other"].isNull() && (oldFlags & ANNOUNCEMENT::Other)) || + (notifications["Other"].isBoolean() && notifications["Other"].asBoolean())) + flags |= ANNOUNCEMENT::Other; + } + + if (!client->SetAnnouncementFlags(flags)) + return BadPermission; + + return GetConfiguration(method, transport, client, parameterObject, result); +} + +JSONRPC_STATUS CJSONRPC::NotifyAll(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result) +{ + if (parameterObject["data"].isNull()) + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Other, + parameterObject["sender"].asString(), + parameterObject["message"].asString()); + else + { + CVariant data = parameterObject["data"]; + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Other, + parameterObject["sender"].asString(), + parameterObject["message"].asString(), data); + } + + return ACK; +} + +std::string CJSONRPC::MethodCall(const std::string &inputString, ITransportLayer *transport, IClient *client) +{ + CVariant inputroot, outputroot, result; + bool hasResponse = false; + + CLog::Log(LOGDEBUG, LOGJSONRPC, "JSONRPC: Incoming request: {}", inputString); + + if (CJSONVariantParser::Parse(inputString, inputroot) && !inputroot.isNull()) + { + if (inputroot.isArray()) + { + if (inputroot.size() <= 0) + { + CLog::Log(LOGERROR, "JSONRPC: Empty batch call"); + BuildResponse(inputroot, InvalidRequest, CVariant(), outputroot); + hasResponse = true; + } + else + { + for (CVariant::const_iterator_array itr = inputroot.begin_array(); + itr != inputroot.end_array(); ++itr) + { + CVariant response; + if (HandleMethodCall(*itr, response, transport, client)) + { + outputroot.append(response); + hasResponse = true; + } + } + } + } + else + hasResponse = HandleMethodCall(inputroot, outputroot, transport, client); + } + else + { + CLog::Log(LOGERROR, "JSONRPC: Failed to parse '{}'", inputString); + BuildResponse(inputroot, ParseError, CVariant(), outputroot); + hasResponse = true; + } + + std::string str; + if (hasResponse) + CJSONVariantWriter::Write(outputroot, str, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_jsonOutputCompact); + + return str; +} + +bool CJSONRPC::HandleMethodCall(const CVariant& request, CVariant& response, ITransportLayer *transport, IClient *client) +{ + JSONRPC_STATUS errorCode = OK; + CVariant result; + bool isNotification = false; + + if (IsProperJSONRPC(request)) + { + isNotification = !request.isMember("id"); + + std::string methodName = request["method"].asString(); + StringUtils::ToLower(methodName); + + JSONRPC::MethodCall method; + CVariant params; + + if ((errorCode = CJSONServiceDescription::CheckCall(methodName.c_str(), request["params"], transport, client, isNotification, method, params)) == OK) + errorCode = method(methodName, transport, client, params, result); + else + result = params; + } + else + { + std::string str; + CJSONVariantWriter::Write(request, str, true); + + CLog::Log(LOGERROR, "JSONRPC: Failed to parse '{}'", str); + errorCode = InvalidRequest; + } + + BuildResponse(request, errorCode, result, response); + + return !isNotification; +} + +inline bool CJSONRPC::IsProperJSONRPC(const CVariant& inputroot) +{ + return inputroot.isMember("jsonrpc") && inputroot["jsonrpc"].isString() && inputroot["jsonrpc"] == CVariant("2.0") && inputroot.isMember("method") && inputroot["method"].isString() && (!inputroot.isMember("params") || inputroot["params"].isArray() || inputroot["params"].isObject()); +} + +inline void CJSONRPC::BuildResponse(const CVariant& request, JSONRPC_STATUS code, const CVariant& result, CVariant& response) +{ + response["jsonrpc"] = "2.0"; + response["id"] = request.isMember("id") ? request["id"] : CVariant(); + + switch (code) + { + case OK: + response["result"] = result; + break; + case ACK: + response["result"] = "OK"; + break; + case InvalidRequest: + response["error"]["code"] = InvalidRequest; + response["error"]["message"] = "Invalid request."; + break; + case InvalidParams: + response["error"]["code"] = InvalidParams; + response["error"]["message"] = "Invalid params."; + if (!result.isNull()) + response["error"]["data"] = result; + break; + case MethodNotFound: + response["error"]["code"] = MethodNotFound; + response["error"]["message"] = "Method not found."; + break; + case ParseError: + response["error"]["code"] = ParseError; + response["error"]["message"] = "Parse error."; + break; + case BadPermission: + response["error"]["code"] = BadPermission; + response["error"]["message"] = "Bad client permission."; + break; + case FailedToExecute: + response["error"]["code"] = FailedToExecute; + response["error"]["message"] = "Failed to execute method."; + break; + default: + response["error"]["code"] = InternalError; + response["error"]["message"] = "Internal error."; + break; + } +} + +void CJSONRPCUtils::NotifyItemUpdated() +{ + CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE, + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow()); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message); +} + +void CJSONRPCUtils::NotifyItemUpdated(const CVideoInfoTag& info, + const std::map<std::string, std::string>& artwork) +{ + CFileItemPtr msgItem(new CFileItem(info)); + if (!artwork.empty()) + msgItem->SetArt(artwork); + CGUIMessage message(GUI_MSG_NOTIFY_ALL, + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(), 0, + GUI_MSG_UPDATE_ITEM, 0, msgItem); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message); +} diff --git a/xbmc/interfaces/json-rpc/JSONRPC.h b/xbmc/interfaces/json-rpc/JSONRPC.h new file mode 100644 index 0000000..2e94576 --- /dev/null +++ b/xbmc/interfaces/json-rpc/JSONRPC.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPCUtils.h" +#include "JSONServiceDescription.h" + +#include <iostream> +#include <map> +#include <stdio.h> +#include <string> + +class CVariant; + +namespace JSONRPC +{ + /*! + \ingroup jsonrpc + \brief JSON RPC handler + + Sets up and manages all needed information to process + JSON-RPC requests and answering with the appropriate + JSON-RPC response (actual response or error message). + */ + class CJSONRPC + { + public: + /*! + \brief Initializes the JSON-RPC handler + */ + static void Initialize(); + + static void Cleanup(); + + /* + \brief Handles an incoming JSON-RPC request + \param inputString received JSON-RPC request + \param transport Transport protocol on which the request arrived + \param client Client which sent the request + \return JSON-RPC response to be sent back to the client + + Parses the received input string for the called method and provided + parameters. If the request does not conform to the JSON-RPC 2.0 + specification an error is returned. Otherwise the parameters provided + in the request are checked for validity and completeness. If the request + is valid and the requested method exists it is called and executed. + */ + static std::string MethodCall(const std::string &inputString, ITransportLayer *transport, IClient *client); + + static JSONRPC_STATUS Introspect(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result); + static JSONRPC_STATUS Version(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result); + static JSONRPC_STATUS Permission(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result); + static JSONRPC_STATUS Ping(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result); + static JSONRPC_STATUS GetConfiguration(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result); + static JSONRPC_STATUS SetConfiguration(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result); + static JSONRPC_STATUS NotifyAll(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result); + + private: + static bool HandleMethodCall(const CVariant& request, CVariant& response, ITransportLayer *transport, IClient *client); + static inline bool IsProperJSONRPC(const CVariant& inputroot); + + inline static void BuildResponse(const CVariant& request, JSONRPC_STATUS code, const CVariant& result, CVariant& response); + + static bool m_initialized; + }; +} diff --git a/xbmc/interfaces/json-rpc/JSONRPCUtils.h b/xbmc/interfaces/json-rpc/JSONRPCUtils.h new file mode 100644 index 0000000..0c0a8f8 --- /dev/null +++ b/xbmc/interfaces/json-rpc/JSONRPCUtils.h @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "IClient.h" +#include "ITransportLayer.h" + +#include <map> +#include <string> + +class CVariant; +class CVideoInfoTag; + +namespace JSONRPC +{ + /*! + \ingroup jsonrpc + \brief Possible statuc codes of a response + to a JSON-RPC request + */ + enum JSONRPC_STATUS + { + OK = 0, + ACK = -1, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, + ParseError = -32700, + //-32099..-32000 Reserved for implementation-defined server-errors. + BadPermission = -32099, + FailedToExecute = -32100 + }; + + /*! + \brief Function pointer for JSON-RPC methods + */ + typedef JSONRPC_STATUS (*MethodCall) (const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result); + + /*! + \ingroup jsonrpc + \brief Permission categories for json rpc methods + + A JSON-RPC method will only be called if the caller + has the correct permissions to execute the method. + The method call needs to be perfectly threadsafe. + */ + enum OperationPermission + { + ReadData = 0x1, + ControlPlayback = 0x2, + ControlNotify = 0x4, + ControlPower = 0x8, + UpdateData = 0x10, + RemoveData = 0x20, + Navigate = 0x40, + WriteFile = 0x80, + ControlSystem = 0x100, + ControlGUI = 0x200, + ManageAddon = 0x400, + ExecuteAddon = 0x800, + ControlPVR = 0x1000 + }; + + const int OPERATION_PERMISSION_ALL = (ReadData | ControlPlayback | ControlNotify | ControlPower | + UpdateData | RemoveData | Navigate | WriteFile | ControlSystem | + ControlGUI | ManageAddon | ExecuteAddon | ControlPVR); + + const int OPERATION_PERMISSION_NOTIFICATION = (ControlPlayback | ControlNotify | ControlPower | UpdateData | + RemoveData | Navigate | WriteFile | ControlSystem | + ControlGUI | ManageAddon | ExecuteAddon | ControlPVR); + + /*! + \brief Returns a string representation for the + given OperationPermission + \param permission Specific OperationPermission + \return String representation of the given OperationPermission + */ + inline const char *PermissionToString(const OperationPermission &permission) + { + switch (permission) + { + case ReadData: + return "ReadData"; + case ControlPlayback: + return "ControlPlayback"; + case ControlNotify: + return "ControlNotify"; + case ControlPower: + return "ControlPower"; + case UpdateData: + return "UpdateData"; + case RemoveData: + return "RemoveData"; + case Navigate: + return "Navigate"; + case WriteFile: + return "WriteFile"; + case ControlSystem: + return "ControlSystem"; + case ControlGUI: + return "ControlGUI"; + case ManageAddon: + return "ManageAddon"; + case ExecuteAddon: + return "ExecuteAddon"; + case ControlPVR: + return "ControlPVR"; + default: + return "Unknown"; + } + } + + /*! + \brief Returns a OperationPermission value for the given + string representation + \param permission String representation of the OperationPermission + \return OperationPermission value of the given string representation + */ + inline OperationPermission StringToPermission(const std::string& permission) + { + if (permission.compare("ControlPlayback") == 0) + return ControlPlayback; + if (permission.compare("ControlNotify") == 0) + return ControlNotify; + if (permission.compare("ControlPower") == 0) + return ControlPower; + if (permission.compare("UpdateData") == 0) + return UpdateData; + if (permission.compare("RemoveData") == 0) + return RemoveData; + if (permission.compare("Navigate") == 0) + return Navigate; + if (permission.compare("WriteFile") == 0) + return WriteFile; + if (permission.compare("ControlSystem") == 0) + return ControlSystem; + if (permission.compare("ControlGUI") == 0) + return ControlGUI; + if (permission.compare("ManageAddon") == 0) + return ManageAddon; + if (permission.compare("ExecuteAddon") == 0) + return ExecuteAddon; + if (permission.compare("ControlPVR") == 0) + return ControlPVR; + + return ReadData; + } + + class CJSONRPCUtils + { + public: + static void NotifyItemUpdated(); + static void NotifyItemUpdated(const CVideoInfoTag& info, + const std::map<std::string, std::string>& artwork); + }; +} diff --git a/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp b/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp new file mode 100644 index 0000000..da100b2 --- /dev/null +++ b/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp @@ -0,0 +1,2153 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "JSONServiceDescription.h" + +#include "AddonsOperations.h" +#include "ApplicationOperations.h" +#include "AudioLibrary.h" +#include "FavouritesOperations.h" +#include "FileOperations.h" +#include "GUIOperations.h" +#include "InputOperations.h" +#include "JSONRPC.h" +#include "PVROperations.h" +#include "PlayerOperations.h" +#include "PlaylistOperations.h" +#include "ProfilesOperations.h" +#include "ServiceDescription.h" +#include "SettingsOperations.h" +#include "SystemOperations.h" +#include "TextureOperations.h" +#include "VideoLibrary.h" +#include "XBMCOperations.h" +#include "utils/JSONVariantParser.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +using namespace JSONRPC; + +std::map<std::string, CVariant> CJSONServiceDescription::m_notifications = std::map<std::string, CVariant>(); +CJSONServiceDescription::CJsonRpcMethodMap CJSONServiceDescription::m_actionMap; +std::map<std::string, JSONSchemaTypeDefinitionPtr> CJSONServiceDescription::m_types = std::map<std::string, JSONSchemaTypeDefinitionPtr>(); +CJSONServiceDescription::IncompleteSchemaDefinitionMap CJSONServiceDescription::m_incompleteDefinitions = CJSONServiceDescription::IncompleteSchemaDefinitionMap(); + +// clang-format off + +JsonRpcMethodMap CJSONServiceDescription::m_methodMaps[] = { +// JSON-RPC + { "JSONRPC.Introspect", CJSONRPC::Introspect }, + { "JSONRPC.Version", CJSONRPC::Version }, + { "JSONRPC.Permission", CJSONRPC::Permission }, + { "JSONRPC.Ping", CJSONRPC::Ping }, + { "JSONRPC.GetConfiguration", CJSONRPC::GetConfiguration }, + { "JSONRPC.SetConfiguration", CJSONRPC::SetConfiguration }, + { "JSONRPC.NotifyAll", CJSONRPC::NotifyAll }, + +// Player + { "Player.GetActivePlayers", CPlayerOperations::GetActivePlayers }, + { "Player.GetPlayers", CPlayerOperations::GetPlayers }, + { "Player.GetProperties", CPlayerOperations::GetProperties }, + { "Player.GetItem", CPlayerOperations::GetItem }, + + { "Player.PlayPause", CPlayerOperations::PlayPause }, + { "Player.Stop", CPlayerOperations::Stop }, + { "Player.GetAudioDelay", CPlayerOperations::GetAudioDelay }, + { "Player.SetAudioDelay", CPlayerOperations::SetAudioDelay }, + { "Player.SetSpeed", CPlayerOperations::SetSpeed }, + { "Player.Seek", CPlayerOperations::Seek }, + { "Player.Move", CPlayerOperations::Move }, + { "Player.Zoom", CPlayerOperations::Zoom }, + { "Player.SetViewMode", CPlayerOperations::SetViewMode }, + { "Player.GetViewMode", CPlayerOperations::GetViewMode }, + { "Player.Rotate", CPlayerOperations::Rotate }, + + { "Player.Open", CPlayerOperations::Open }, + { "Player.GoTo", CPlayerOperations::GoTo }, + { "Player.SetShuffle", CPlayerOperations::SetShuffle }, + { "Player.SetRepeat", CPlayerOperations::SetRepeat }, + { "Player.SetPartymode", CPlayerOperations::SetPartymode }, + + { "Player.SetAudioStream", CPlayerOperations::SetAudioStream }, + { "Player.AddSubtitle", CPlayerOperations::AddSubtitle }, + { "Player.SetSubtitle", CPlayerOperations::SetSubtitle }, + { "Player.SetVideoStream", CPlayerOperations::SetVideoStream }, + +// Playlist + { "Playlist.GetPlaylists", CPlaylistOperations::GetPlaylists }, + { "Playlist.GetProperties", CPlaylistOperations::GetProperties }, + { "Playlist.GetItems", CPlaylistOperations::GetItems }, + { "Playlist.Add", CPlaylistOperations::Add }, + { "Playlist.Insert", CPlaylistOperations::Insert }, + { "Playlist.Clear", CPlaylistOperations::Clear }, + { "Playlist.Remove", CPlaylistOperations::Remove }, + { "Playlist.Swap", CPlaylistOperations::Swap }, + +// Files + { "Files.GetSources", CFileOperations::GetRootDirectory }, + { "Files.GetDirectory", CFileOperations::GetDirectory }, + { "Files.GetFileDetails", CFileOperations::GetFileDetails }, + { "Files.SetFileDetails", CFileOperations::SetFileDetails }, + { "Files.PrepareDownload", CFileOperations::PrepareDownload }, + { "Files.Download", CFileOperations::Download }, + +// Music Library + { "AudioLibrary.GetProperties", CAudioLibrary::GetProperties }, + { "AudioLibrary.GetArtists", CAudioLibrary::GetArtists }, + { "AudioLibrary.GetArtistDetails", CAudioLibrary::GetArtistDetails }, + { "AudioLibrary.GetAlbums", CAudioLibrary::GetAlbums }, + { "AudioLibrary.GetAlbumDetails", CAudioLibrary::GetAlbumDetails }, + { "AudioLibrary.GetSongs", CAudioLibrary::GetSongs }, + { "AudioLibrary.GetSongDetails", CAudioLibrary::GetSongDetails }, + { "AudioLibrary.GetRecentlyAddedAlbums", CAudioLibrary::GetRecentlyAddedAlbums }, + { "AudioLibrary.GetRecentlyAddedSongs", CAudioLibrary::GetRecentlyAddedSongs }, + { "AudioLibrary.GetRecentlyPlayedAlbums", CAudioLibrary::GetRecentlyPlayedAlbums }, + { "AudioLibrary.GetRecentlyPlayedSongs", CAudioLibrary::GetRecentlyPlayedSongs }, + { "AudioLibrary.GetGenres", CAudioLibrary::GetGenres }, + { "AudioLibrary.GetRoles", CAudioLibrary::GetRoles }, + { "AudioLibrary.GetSources", CAudioLibrary::GetSources }, + { "AudioLibrary.GetAvailableArtTypes", CAudioLibrary::GetAvailableArtTypes }, + { "AudioLibrary.GetAvailableArt", CAudioLibrary::GetAvailableArt }, + { "AudioLibrary.SetArtistDetails", CAudioLibrary::SetArtistDetails }, + { "AudioLibrary.SetAlbumDetails", CAudioLibrary::SetAlbumDetails }, + { "AudioLibrary.SetSongDetails", CAudioLibrary::SetSongDetails }, + { "AudioLibrary.Scan", CAudioLibrary::Scan }, + { "AudioLibrary.Export", CAudioLibrary::Export }, + { "AudioLibrary.Clean", CAudioLibrary::Clean }, + +// Video Library + { "VideoLibrary.GetGenres", CVideoLibrary::GetGenres }, + { "VideoLibrary.GetTags", CVideoLibrary::GetTags }, + { "VideoLibrary.GetAvailableArtTypes", CVideoLibrary::GetAvailableArtTypes }, + { "VideoLibrary.GetAvailableArt", CVideoLibrary::GetAvailableArt }, + { "VideoLibrary.GetMovies", CVideoLibrary::GetMovies }, + { "VideoLibrary.GetMovieDetails", CVideoLibrary::GetMovieDetails }, + { "VideoLibrary.GetMovieSets", CVideoLibrary::GetMovieSets }, + { "VideoLibrary.GetMovieSetDetails", CVideoLibrary::GetMovieSetDetails }, + { "VideoLibrary.GetTVShows", CVideoLibrary::GetTVShows }, + { "VideoLibrary.GetTVShowDetails", CVideoLibrary::GetTVShowDetails }, + { "VideoLibrary.GetSeasons", CVideoLibrary::GetSeasons }, + { "VideoLibrary.GetSeasonDetails", CVideoLibrary::GetSeasonDetails }, + { "VideoLibrary.GetEpisodes", CVideoLibrary::GetEpisodes }, + { "VideoLibrary.GetEpisodeDetails", CVideoLibrary::GetEpisodeDetails }, + { "VideoLibrary.GetMusicVideos", CVideoLibrary::GetMusicVideos }, + { "VideoLibrary.GetMusicVideoDetails", CVideoLibrary::GetMusicVideoDetails }, + { "VideoLibrary.GetRecentlyAddedMovies", CVideoLibrary::GetRecentlyAddedMovies }, + { "VideoLibrary.GetRecentlyAddedEpisodes", CVideoLibrary::GetRecentlyAddedEpisodes }, + { "VideoLibrary.GetRecentlyAddedMusicVideos", CVideoLibrary::GetRecentlyAddedMusicVideos }, + { "VideoLibrary.GetInProgressTVShows", CVideoLibrary::GetInProgressTVShows }, + { "VideoLibrary.SetMovieDetails", CVideoLibrary::SetMovieDetails }, + { "VideoLibrary.SetMovieSetDetails", CVideoLibrary::SetMovieSetDetails }, + { "VideoLibrary.SetTVShowDetails", CVideoLibrary::SetTVShowDetails }, + { "VideoLibrary.SetSeasonDetails", CVideoLibrary::SetSeasonDetails }, + { "VideoLibrary.SetEpisodeDetails", CVideoLibrary::SetEpisodeDetails }, + { "VideoLibrary.SetMusicVideoDetails", CVideoLibrary::SetMusicVideoDetails }, + { "VideoLibrary.RefreshMovie", CVideoLibrary::RefreshMovie }, + { "VideoLibrary.RefreshTVShow", CVideoLibrary::RefreshTVShow }, + { "VideoLibrary.RefreshEpisode", CVideoLibrary::RefreshEpisode }, + { "VideoLibrary.RefreshMusicVideo", CVideoLibrary::RefreshMusicVideo }, + { "VideoLibrary.RemoveMovie", CVideoLibrary::RemoveMovie }, + { "VideoLibrary.RemoveTVShow", CVideoLibrary::RemoveTVShow }, + { "VideoLibrary.RemoveEpisode", CVideoLibrary::RemoveEpisode }, + { "VideoLibrary.RemoveMusicVideo", CVideoLibrary::RemoveMusicVideo }, + { "VideoLibrary.Scan", CVideoLibrary::Scan }, + { "VideoLibrary.Export", CVideoLibrary::Export }, + { "VideoLibrary.Clean", CVideoLibrary::Clean }, + +// Addon operations + { "Addons.GetAddons", CAddonsOperations::GetAddons }, + { "Addons.GetAddonDetails", CAddonsOperations::GetAddonDetails }, + { "Addons.SetAddonEnabled", CAddonsOperations::SetAddonEnabled }, + { "Addons.ExecuteAddon", CAddonsOperations::ExecuteAddon }, + +// GUI operations + { "GUI.GetProperties", CGUIOperations::GetProperties }, + { "GUI.ActivateWindow", CGUIOperations::ActivateWindow }, + { "GUI.ShowNotification", CGUIOperations::ShowNotification }, + { "GUI.SetFullscreen", CGUIOperations::SetFullscreen }, + { "GUI.SetStereoscopicMode", CGUIOperations::SetStereoscopicMode }, + { "GUI.GetStereoscopicModes", CGUIOperations::GetStereoscopicModes }, + +// PVR operations + { "PVR.GetProperties", CPVROperations::GetProperties }, + { "PVR.GetChannelGroups", CPVROperations::GetChannelGroups }, + { "PVR.GetChannelGroupDetails", CPVROperations::GetChannelGroupDetails }, + { "PVR.GetChannels", CPVROperations::GetChannels }, + { "PVR.GetChannelDetails", CPVROperations::GetChannelDetails }, + { "PVR.GetClients", CPVROperations::GetClients }, + { "PVR.GetBroadcasts", CPVROperations::GetBroadcasts }, + { "PVR.GetBroadcastDetails", CPVROperations::GetBroadcastDetails }, + { "PVR.GetBroadcastIsPlayable", CPVROperations::GetBroadcastIsPlayable }, + { "PVR.GetTimers", CPVROperations::GetTimers }, + { "PVR.GetTimerDetails", CPVROperations::GetTimerDetails }, + { "PVR.GetRecordings", CPVROperations::GetRecordings }, + { "PVR.GetRecordingDetails", CPVROperations::GetRecordingDetails }, + { "PVR.AddTimer", CPVROperations::AddTimer }, + { "PVR.DeleteTimer", CPVROperations::DeleteTimer }, + { "PVR.ToggleTimer", CPVROperations::ToggleTimer }, + { "PVR.Record", CPVROperations::Record }, + { "PVR.Scan", CPVROperations::Scan }, + +// Profiles operations + { "Profiles.GetProfiles", CProfilesOperations::GetProfiles}, + { "Profiles.GetCurrentProfile", CProfilesOperations::GetCurrentProfile}, + { "Profiles.LoadProfile", CProfilesOperations::LoadProfile}, + +// System operations + { "System.GetProperties", CSystemOperations::GetProperties }, + { "System.EjectOpticalDrive", CSystemOperations::EjectOpticalDrive }, + { "System.Shutdown", CSystemOperations::Shutdown }, + { "System.Suspend", CSystemOperations::Suspend }, + { "System.Hibernate", CSystemOperations::Hibernate }, + { "System.Reboot", CSystemOperations::Reboot }, + +// Input operations + { "Input.SendText", CInputOperations::SendText }, + { "Input.ExecuteAction", CInputOperations::ExecuteAction }, + { "Input.ButtonEvent", CInputOperations::ButtonEvent }, + { "Input.Left", CInputOperations::Left }, + { "Input.Right", CInputOperations::Right }, + { "Input.Down", CInputOperations::Down }, + { "Input.Up", CInputOperations::Up }, + { "Input.Select", CInputOperations::Select }, + { "Input.Back", CInputOperations::Back }, + { "Input.ContextMenu", CInputOperations::ContextMenu }, + { "Input.Info", CInputOperations::Info }, + { "Input.Home", CInputOperations::Home }, + { "Input.ShowCodec", CInputOperations::ShowCodec }, + { "Input.ShowOSD", CInputOperations::ShowOSD }, + { "Input.ShowPlayerProcessInfo", CInputOperations::ShowPlayerProcessInfo }, + +// Application operations + { "Application.GetProperties", CApplicationOperations::GetProperties }, + { "Application.SetVolume", CApplicationOperations::SetVolume }, + { "Application.SetMute", CApplicationOperations::SetMute }, + { "Application.Quit", CApplicationOperations::Quit }, + +// Favourites operations + { "Favourites.GetFavourites", CFavouritesOperations::GetFavourites }, + { "Favourites.AddFavourite", CFavouritesOperations::AddFavourite }, + +// Textures operations + { "Textures.GetTextures", CTextureOperations::GetTextures }, + { "Textures.RemoveTexture", CTextureOperations::RemoveTexture }, + +// Settings operations + { "Settings.GetSections", CSettingsOperations::GetSections }, + { "Settings.GetCategories", CSettingsOperations::GetCategories }, + { "Settings.GetSettings", CSettingsOperations::GetSettings }, + { "Settings.GetSettingValue", CSettingsOperations::GetSettingValue }, + { "Settings.SetSettingValue", CSettingsOperations::SetSettingValue }, + { "Settings.ResetSettingValue", CSettingsOperations::ResetSettingValue }, + { "Settings.GetSkinSettings", CSettingsOperations::GetSkinSettings }, + { "Settings.GetSkinSettingValue", CSettingsOperations::GetSkinSettingValue }, + { "Settings.SetSkinSettingValue", CSettingsOperations::SetSkinSettingValue }, + +// XBMC operations + { "XBMC.GetInfoLabels", CXBMCOperations::GetInfoLabels }, + { "XBMC.GetInfoBooleans", CXBMCOperations::GetInfoBooleans } +}; + +// clang-format on + +JSONSchemaTypeDefinition::JSONSchemaTypeDefinition() + : missingReference(), + name(), + ID(), + referencedType(nullptr), + extends(), + description(), + unionTypes(), + defaultValue(), + minimum(-std::numeric_limits<double>::max()), + maximum(std::numeric_limits<double>::max()), + enums(), + items(), + additionalItems(), + properties(), + additionalProperties(nullptr) +{ } + +bool JSONSchemaTypeDefinition::Parse(const CVariant &value, bool isParameter /* = false */) +{ + bool hasReference = false; + + // Check if the type of the parameter defines a json reference + // to a type defined somewhere else + if (value.isMember("$ref") && value["$ref"].isString()) + { + // Get the name of the referenced type + std::string refType = value["$ref"].asString(); + // Check if the referenced type exists + JSONSchemaTypeDefinitionPtr referencedTypeDef = CJSONServiceDescription::GetType(refType); + if (refType.length() <= 0 || referencedTypeDef.get() == NULL) + { + CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type {} references an unknown type {}", name, + refType); + missingReference = refType; + return false; + } + + std::string typeName = name; + *this = *referencedTypeDef; + if (!typeName.empty()) + name = typeName; + referencedType = referencedTypeDef; + hasReference = true; + } + else if (value.isMember("id") && value["id"].isString()) + ID = GetString(value["id"], ""); + + // Check if the "required" field has been defined + optional = value.isMember("required") && value["required"].isBoolean() ? !value["required"].asBoolean() : true; + + // Get the "description" + if (!hasReference || (value.isMember("description") && value["description"].isString())) + description = GetString(value["description"], ""); + + if (hasReference) + { + // If there is a specific default value, read it + if (value.isMember("default") && IsType(value["default"], type)) + { + bool ok = false; + if (enums.size() <= 0) + ok = true; + // If the type has an enum definition we must make + // sure that the default value is a valid enum value + else + { + for (const auto& itr : enums) + { + if (value["default"] == itr) + { + ok = true; + break; + } + } + } + + if (ok) + defaultValue = value["default"]; + } + + return true; + } + + // Check whether this type extends an existing type + if (value.isMember("extends")) + { + if (value["extends"].isString()) + { + std::string extendsName = GetString(value["extends"], ""); + if (!extendsName.empty()) + { + JSONSchemaTypeDefinitionPtr extendedTypeDef = CJSONServiceDescription::GetType(extendsName); + if (extendedTypeDef.get() == NULL) + { + CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type {} extends an unknown type {}", name, + extendsName); + missingReference = extendsName; + return false; + } + + type = extendedTypeDef->type; + extends.push_back(extendedTypeDef); + } + } + else if (value["extends"].isArray()) + { + JSONSchemaType extendedType = AnyValue; + for (unsigned int extendsIndex = 0; extendsIndex < value["extends"].size(); extendsIndex++) + { + std::string extendsName = GetString(value["extends"][extendsIndex], ""); + if (!extendsName.empty()) + { + JSONSchemaTypeDefinitionPtr extendedTypeDef = CJSONServiceDescription::GetType(extendsName); + if (extendedTypeDef.get() == NULL) + { + extends.clear(); + CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type {} extends an unknown type {}", name, + extendsName); + missingReference = extendsName; + return false; + } + + if (extendsIndex == 0) + extendedType = extendedTypeDef->type; + else if (extendedType != extendedTypeDef->type) + { + extends.clear(); + CLog::Log(LOGDEBUG, + "JSONRPC: JSON schema type {} extends multiple JSON schema types of " + "mismatching types", + name); + return false; + } + + extends.push_back(extendedTypeDef); + } + } + + type = extendedType; + } + } + + // Only read the "type" attribute if it's + // not an extending type + if (extends.size() <= 0) + { + // Get the defined type of the parameter + if (!CJSONServiceDescription::parseJSONSchemaType(value["type"], unionTypes, type, missingReference)) + return false; + } + + if (HasType(type, ObjectValue)) + { + // If the type definition is of type "object" + // and has a "properties" definition we need + // to handle these as well + if (value.isMember("properties") && value["properties"].isObject()) + { + // Get all child elements of the "properties" + // object and loop through them + for (CVariant::const_iterator_map itr = value["properties"].begin_map(); itr != value["properties"].end_map(); ++itr) + { + // Create a new type definition, store the name + // of the current property into it, parse it + // recursively and add its default value + // to the current type's default value + JSONSchemaTypeDefinitionPtr propertyType = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + propertyType->name = itr->first; + if (!propertyType->Parse(itr->second)) + { + missingReference = propertyType->missingReference; + return false; + } + defaultValue[itr->first] = propertyType->defaultValue; + properties.add(propertyType); + } + } + + hasAdditionalProperties = true; + additionalProperties = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + if (value.isMember("additionalProperties")) + { + if (value["additionalProperties"].isBoolean()) + { + hasAdditionalProperties = value["additionalProperties"].asBoolean(); + if (!hasAdditionalProperties) + { + additionalProperties.reset(); + } + } + else if (value["additionalProperties"].isObject() && !value["additionalProperties"].isNull()) + { + if (!additionalProperties->Parse(value["additionalProperties"])) + { + missingReference = additionalProperties->missingReference; + hasAdditionalProperties = false; + additionalProperties.reset(); + + CLog::Log(LOGDEBUG, "JSONRPC: Invalid additionalProperties schema definition in type {}", + name); + return false; + } + } + else + { + CLog::Log(LOGDEBUG, "JSONRPC: Invalid additionalProperties definition in type {}", name); + return false; + } + } + } + + // If the defined parameter is an array + // we need to check for detailed definitions + // of the array items + if (HasType(type, ArrayValue)) + { + // Check for "uniqueItems" field + if (value.isMember("uniqueItems") && value["uniqueItems"].isBoolean()) + uniqueItems = value["uniqueItems"].asBoolean(); + else + uniqueItems = false; + + // Check for "additionalItems" field + if (value.isMember("additionalItems")) + { + // If it is an object, there is only one schema for it + if (value["additionalItems"].isObject()) + { + JSONSchemaTypeDefinitionPtr additionalItem = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + if (additionalItem->Parse(value["additionalItems"])) + additionalItems.push_back(additionalItem); + else + { + CLog::Log(LOGDEBUG, "Invalid \"additionalItems\" value for type {}", name); + missingReference = additionalItem->missingReference; + return false; + } + } + // If it is an array there may be multiple schema definitions + else if (value["additionalItems"].isArray()) + { + for (unsigned int itemIndex = 0; itemIndex < value["additionalItems"].size(); itemIndex++) + { + JSONSchemaTypeDefinitionPtr additionalItem = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + + if (additionalItem->Parse(value["additionalItems"][itemIndex])) + additionalItems.push_back(additionalItem); + else + { + CLog::Log(LOGDEBUG, "Invalid \"additionalItems\" value (item {}) for type {}", + itemIndex, name); + missingReference = additionalItem->missingReference; + return false; + } + } + } + // If it is not a (array of) schema and not a bool (default value is false) + // it has an invalid value + else if (!value["additionalItems"].isBoolean()) + { + CLog::Log(LOGDEBUG, "Invalid \"additionalItems\" definition for type {}", name); + return false; + } + } + + // If the "items" field is a single object + // we can parse that directly + if (value.isMember("items")) + { + if (value["items"].isObject()) + { + JSONSchemaTypeDefinitionPtr item = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + if (!item->Parse(value["items"])) + { + CLog::Log(LOGDEBUG, "Invalid item definition in \"items\" for type {}", name); + missingReference = item->missingReference; + return false; + } + items.push_back(item); + } + // Otherwise if it is an array we need to + // parse all elements and store them + else if (value["items"].isArray()) + { + for (CVariant::const_iterator_array itemItr = value["items"].begin_array(); itemItr != value["items"].end_array(); ++itemItr) + { + JSONSchemaTypeDefinitionPtr item = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + if (!item->Parse(*itemItr)) + { + CLog::Log(LOGDEBUG, "Invalid item definition in \"items\" array for type {}", name); + missingReference = item->missingReference; + return false; + } + items.push_back(item); + } + } + } + + minItems = (unsigned int)value["minItems"].asUnsignedInteger(0); + maxItems = (unsigned int)value["maxItems"].asUnsignedInteger(0); + } + + if (HasType(type, NumberValue) || HasType(type, IntegerValue)) + { + if ((type & NumberValue) == NumberValue) + { + minimum = value["minimum"].asDouble(-std::numeric_limits<double>::max()); + maximum = value["maximum"].asDouble(std::numeric_limits<double>::max()); + } + else if ((type & IntegerValue) == IntegerValue) + { + minimum = (double)value["minimum"].asInteger(std::numeric_limits<int>::min()); + maximum = (double)value["maximum"].asInteger(std::numeric_limits<int>::max()); + } + + exclusiveMinimum = value["exclusiveMinimum"].asBoolean(false); + exclusiveMaximum = value["exclusiveMaximum"].asBoolean(false); + divisibleBy = (unsigned int)value["divisibleBy"].asUnsignedInteger(0); + } + + if (HasType(type, StringValue)) + { + minLength = (int)value["minLength"].asInteger(-1); + maxLength = (int)value["maxLength"].asInteger(-1); + } + + // If the type definition is neither an + // "object" nor an "array" we can check + // for an "enum" definition + if (value.isMember("enum") && value["enum"].isArray()) + { + // Loop through all elements in the "enum" array + for (CVariant::const_iterator_array enumItr = value["enum"].begin_array(); enumItr != value["enum"].end_array(); ++enumItr) + { + // Check for duplicates and eliminate them + bool approved = true; + for (unsigned int approvedIndex = 0; approvedIndex < enums.size(); approvedIndex++) + { + if (*enumItr == enums.at(approvedIndex)) + { + approved = false; + break; + } + } + + // Only add the current item to the enum value + // list if it is not duplicate + if (approved) + enums.push_back(*enumItr); + } + } + + if (type != ObjectValue) + { + // If there is a definition for a default value and its type + // matches the type of the parameter we can parse it + bool ok = false; + if (value.isMember("default") && IsType(value["default"], type)) + { + if (enums.size() <= 0) + ok = true; + // If the type has an enum definition we must make + // sure that the default value is a valid enum value + else + { + for (std::vector<CVariant>::const_iterator itr = enums.begin(); itr != enums.end(); ++itr) + { + if (value["default"] == *itr) + { + ok = true; + break; + } + } + } + } + + if (ok) + defaultValue = value["default"]; + else + { + // If the type of the default value definition does not + // match the type of the parameter we have to log this + if (value.isMember("default") && !IsType(value["default"], type)) + CLog::Log(LOGDEBUG, "JSONRPC: Parameter {} has an invalid default value", name); + + // If the type contains an "enum" we need to get the + // default value from the first enum value + if (enums.size() > 0) + defaultValue = enums.at(0); + // otherwise set a default value instead + else + SetDefaultValue(defaultValue, type); + } + } + + return true; +} + +JSONRPC_STATUS JSONSchemaTypeDefinition::Check(const CVariant& value, + CVariant& outputValue, + CVariant& errorData) const +{ + if (!name.empty()) + errorData["name"] = name; + SchemaValueTypeToJson(type, errorData["type"]); + std::string errorMessage; + + // Let's check the type of the provided parameter + if (!IsType(value, type)) + { + errorMessage = StringUtils::Format("Invalid type {} received", ValueTypeToString(value.type())); + errorData["message"] = errorMessage.c_str(); + return InvalidParams; + } + else if (value.isNull() && !HasType(type, NullValue)) + { + errorData["message"] = "Received value is null"; + return InvalidParams; + } + + // Let's check if we have to handle a union type + if (unionTypes.size() > 0) + { + bool ok = false; + for (unsigned int unionIndex = 0; unionIndex < unionTypes.size(); unionIndex++) + { + CVariant dummyError; + CVariant testOutput = outputValue; + if (unionTypes.at(unionIndex)->Check(value, testOutput, dummyError) == OK) + { + ok = true; + outputValue = testOutput; + break; + } + } + + if (!ok) + { + errorData["message"] = "Received value does not match any of the union type definitions"; + return InvalidParams; + } + } + + // First we need to check if this type extends another + // type and if so we need to check against the extended + // type first + if (extends.size() > 0) + { + for (unsigned int extendsIndex = 0; extendsIndex < extends.size(); extendsIndex++) + { + JSONRPC_STATUS status = extends.at(extendsIndex)->Check(value, outputValue, errorData); + + if (status != OK) + { + CLog::Log(LOGDEBUG, "JSONRPC: Value does not match extended type {} of type {}", + extends.at(extendsIndex)->ID, name); + errorMessage = StringUtils::Format("value does not match extended type {}", + extends.at(extendsIndex)->ID); + errorData["message"] = errorMessage.c_str(); + return status; + } + } + } + + // If it is an array we need to + // - check the type of every element ("items") + // - check if they need to be unique ("uniqueItems") + if (HasType(type, ArrayValue) && value.isArray()) + { + outputValue = CVariant(CVariant::VariantTypeArray); + // Check the number of items against minItems and maxItems + if ((minItems > 0 && value.size() < minItems) || (maxItems > 0 && value.size() > maxItems)) + { + CLog::Log( + LOGDEBUG, + "JSONRPC: Number of array elements does not match minItems and/or maxItems in type {}", + name); + if (minItems > 0 && maxItems > 0) + errorMessage = StringUtils::Format("Between {} and {} array items expected but {} received", + minItems, maxItems, value.size()); + else if (minItems > 0) + errorMessage = StringUtils::Format("At least {} array items expected but only {} received", + minItems, value.size()); + else + errorMessage = StringUtils::Format("Only {} array items expected but {} received", maxItems, + value.size()); + errorData["message"] = errorMessage.c_str(); + return InvalidParams; + } + + if (items.size() == 0) + outputValue = value; + else if (items.size() == 1) + { + JSONSchemaTypeDefinitionPtr itemType = items.at(0); + + // Loop through all array elements + for (unsigned int arrayIndex = 0; arrayIndex < value.size(); arrayIndex++) + { + CVariant temp; + JSONRPC_STATUS status = itemType->Check(value[arrayIndex], temp, errorData["property"]); + outputValue.push_back(temp); + if (status != OK) + { + CLog::Log(LOGDEBUG, "JSONRPC: Array element at index {} does not match in type {}", + arrayIndex, name); + errorMessage = + StringUtils::Format("array element at index {} does not match", arrayIndex); + errorData["message"] = errorMessage.c_str(); + return status; + } + } + } + // We have more than one element in "items" + // so we have tuple typing, which means that + // every element in the value array must match + // with the type at the same position in the + // "items" array + else + { + // If the number of elements in the value array + // does not match the number of elements in the + // "items" array and additional items are not + // allowed there is no need to check every element + if (value.size() < items.size() || (value.size() != items.size() && additionalItems.size() == 0)) + { + CLog::Log(LOGDEBUG, "JSONRPC: One of the array elements does not match in type {}", name); + errorMessage = StringUtils::Format("{0} array elements expected but {1} received", items.size(), value.size()); + errorData["message"] = errorMessage.c_str(); + return InvalidParams; + } + + // Loop through all array elements until there + // are either no more schemas in the "items" + // array or no more elements in the value's array + unsigned int arrayIndex; + for (arrayIndex = 0; arrayIndex < std::min(items.size(), (size_t)value.size()); arrayIndex++) + { + JSONRPC_STATUS status = items.at(arrayIndex)->Check(value[arrayIndex], outputValue[arrayIndex], errorData["property"]); + if (status != OK) + { + CLog::Log( + LOGDEBUG, + "JSONRPC: Array element at index {} does not match with items schema in type {}", + arrayIndex, name); + return status; + } + } + + if (additionalItems.size() > 0) + { + // Loop through the rest of the elements + // in the array and check them against the + // "additionalItems" + for (; arrayIndex < value.size(); arrayIndex++) + { + bool ok = false; + for (unsigned int additionalIndex = 0; additionalIndex < additionalItems.size(); additionalIndex++) + { + CVariant dummyError; + if (additionalItems.at(additionalIndex)->Check(value[arrayIndex], outputValue[arrayIndex], dummyError) == OK) + { + ok = true; + break; + } + } + + if (!ok) + { + CLog::Log(LOGDEBUG, + "JSONRPC: Array contains non-conforming additional items in type {}", name); + errorMessage = StringUtils::Format( + "Array element at index {} does not match the \"additionalItems\" schema", + arrayIndex); + errorData["message"] = errorMessage.c_str(); + return InvalidParams; + } + } + } + } + + // If every array element is unique we need to check each one + if (uniqueItems) + { + for (unsigned int checkingIndex = 0; checkingIndex < outputValue.size(); checkingIndex++) + { + for (unsigned int checkedIndex = checkingIndex + 1; checkedIndex < outputValue.size(); checkedIndex++) + { + // If two elements are the same they are not unique + if (outputValue[checkingIndex] == outputValue[checkedIndex]) + { + CLog::Log(LOGDEBUG, "JSONRPC: Not unique array element at index {} and {} in type {}", + checkingIndex, checkedIndex, name); + errorMessage = StringUtils::Format( + "Array element at index {} is not unique (same as array element at index {})", + checkingIndex, checkedIndex); + errorData["message"] = errorMessage.c_str(); + return InvalidParams; + } + } + } + } + + return OK; + } + + // If it is an object we need to check every element + // against the defined "properties" + if (HasType(type, ObjectValue) && value.isObject()) + { + unsigned int handled = 0; + JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesEnd = properties.end(); + JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesIterator; + for (propertiesIterator = properties.begin(); propertiesIterator != propertiesEnd; ++propertiesIterator) + { + if (value.isMember(propertiesIterator->second->name)) + { + JSONRPC_STATUS status = propertiesIterator->second->Check(value[propertiesIterator->second->name], outputValue[propertiesIterator->second->name], errorData["property"]); + if (status != OK) + { + CLog::Log(LOGDEBUG, "JSONRPC: Invalid property \"{}\" in type {}", + propertiesIterator->second->name, name); + return status; + } + handled++; + } + else if (propertiesIterator->second->optional) + outputValue[propertiesIterator->second->name] = propertiesIterator->second->defaultValue; + else + { + errorData["property"]["name"] = propertiesIterator->second->name.c_str(); + errorData["property"]["type"] = SchemaValueTypeToString(propertiesIterator->second->type); + errorData["message"] = "Missing property"; + return InvalidParams; + } + } + + // Additional properties are not allowed + if (handled < value.size()) + { + // If additional properties are allowed we need to check if + // they match the defined schema + if (hasAdditionalProperties && additionalProperties != NULL) + { + CVariant::const_iterator_map iter; + CVariant::const_iterator_map iterEnd = value.end_map(); + for (iter = value.begin_map(); iter != iterEnd; ++iter) + { + if (properties.find(iter->first) != properties.end()) + continue; + + // If the additional property is of type "any" + // we can simply copy its value to the output + // object + if (additionalProperties->type == AnyValue) + { + outputValue[iter->first] = value[iter->first]; + continue; + } + + JSONRPC_STATUS status = additionalProperties->Check(value[iter->first], outputValue[iter->first], errorData["property"]); + if (status != OK) + { + CLog::Log(LOGDEBUG, "JSONRPC: Invalid additional property \"{}\" in type {}", + iter->first, name); + return status; + } + } + } + // If we still have unchecked properties but additional + // properties are not allowed, we have invalid parameters + else if (!hasAdditionalProperties || additionalProperties == NULL) + { + errorData["message"] = "Unexpected additional properties received"; + errorData.erase("property"); + return InvalidParams; + } + } + + return OK; + } + + // It's neither an array nor an object + + // If it can only take certain values ("enum") + // we need to check against those + if (enums.size() > 0) + { + bool valid = false; + for (const auto& enumItr : enums) + { + if (enumItr == value) + { + valid = true; + break; + } + } + + if (!valid) + { + CLog::Log(LOGDEBUG, "JSONRPC: Value does not match any of the enum values in type {}", name); + errorData["message"] = "Received value does not match any of the defined enum values"; + return InvalidParams; + } + } + + // If we have a number or an integer type, we need + // to check the minimum and maximum values + if ((HasType(type, NumberValue) && value.isDouble()) || (HasType(type, IntegerValue) && value.isInteger())) + { + double numberValue; + if (value.isDouble()) + numberValue = value.asDouble(); + else + numberValue = (double)value.asInteger(); + // Check minimum + if ((exclusiveMinimum && numberValue <= minimum) || (!exclusiveMinimum && numberValue < minimum) || + // Check maximum + (exclusiveMaximum && numberValue >= maximum) || (!exclusiveMaximum && numberValue > maximum)) + { + CLog::Log(LOGDEBUG, "JSONRPC: Value does not lay between minimum and maximum in type {}", + name); + if (value.isDouble()) + errorMessage = + StringUtils::Format("Value between {:f} ({}) and {:f} ({}) expected but {:f} received", + minimum, exclusiveMinimum ? "exclusive" : "inclusive", maximum, + exclusiveMaximum ? "exclusive" : "inclusive", numberValue); + else + errorMessage = StringUtils::Format( + "Value between {} ({}) and {} ({}) expected but {} received", (int)minimum, + exclusiveMinimum ? "exclusive" : "inclusive", (int)maximum, + exclusiveMaximum ? "exclusive" : "inclusive", (int)numberValue); + errorData["message"] = errorMessage.c_str(); + return InvalidParams; + } + // Check divisibleBy + if ((HasType(type, IntegerValue) && divisibleBy > 0 && ((int)numberValue % divisibleBy) != 0)) + { + CLog::Log(LOGDEBUG, "JSONRPC: Value does not meet divisibleBy requirements in type {}", name); + errorMessage = StringUtils::Format("Value should be divisible by {} but {} received", + divisibleBy, (int)numberValue); + errorData["message"] = errorMessage.c_str(); + return InvalidParams; + } + } + + // If we have a string, we need to check the length + if (HasType(type, StringValue) && value.isString()) + { + int size = static_cast<int>(value.asString().size()); + if (size < minLength) + { + CLog::Log(LOGDEBUG, "JSONRPC: Value does not meet minLength requirements in type {}", name); + errorMessage = StringUtils::Format( + "Value should have a minimum length of {} but has a length of {}", minLength, size); + errorData["message"] = errorMessage.c_str(); + return InvalidParams; + } + + if (maxLength >= 0 && size > maxLength) + { + CLog::Log(LOGDEBUG, "JSONRPC: Value does not meet maxLength requirements in type {}", name); + errorMessage = StringUtils::Format( + "Value should have a maximum length of {} but has a length of {}", maxLength, size); + errorData["message"] = errorMessage.c_str(); + return InvalidParams; + } + } + + // Otherwise it can have any value + outputValue = value; + return OK; +} + +void JSONSchemaTypeDefinition::Print(bool isParameter, bool isGlobal, bool printDefault, bool printDescriptions, CVariant &output) const +{ + bool typeReference = false; + + // Printing general fields + if (isParameter) + output["name"] = name; + + if (isGlobal) + output["id"] = ID; + else if (!ID.empty()) + { + output["$ref"] = ID; + typeReference = true; + } + + if (printDescriptions && !description.empty()) + output["description"] = description; + + if (isParameter || printDefault) + { + if (!optional) + output["required"] = true; + if (optional && type != ObjectValue && type != ArrayValue) + output["default"] = defaultValue; + } + + if (!typeReference) + { + if (extends.size() == 1) + { + output["extends"] = extends.at(0)->ID; + } + else if (extends.size() > 1) + { + output["extends"] = CVariant(CVariant::VariantTypeArray); + for (unsigned int extendsIndex = 0; extendsIndex < extends.size(); extendsIndex++) + output["extends"].append(extends.at(extendsIndex)->ID); + } + else if (unionTypes.size() > 0) + { + output["type"] = CVariant(CVariant::VariantTypeArray); + for (unsigned int unionIndex = 0; unionIndex < unionTypes.size(); unionIndex++) + { + CVariant unionOutput = CVariant(CVariant::VariantTypeObject); + unionTypes.at(unionIndex)->Print(false, false, false, printDescriptions, unionOutput); + output["type"].append(unionOutput); + } + } + else + CJSONUtils::SchemaValueTypeToJson(type, output["type"]); + + // Printing enum field + if (enums.size() > 0) + { + output["enums"] = CVariant(CVariant::VariantTypeArray); + for (unsigned int enumIndex = 0; enumIndex < enums.size(); enumIndex++) + output["enums"].append(enums.at(enumIndex)); + } + + // Printing integer/number fields + if (CJSONUtils::HasType(type, IntegerValue) || CJSONUtils::HasType(type, NumberValue)) + { + if (CJSONUtils::HasType(type, NumberValue)) + { + if (minimum > -std::numeric_limits<double>::max()) + output["minimum"] = minimum; + if (maximum < std::numeric_limits<double>::max()) + output["maximum"] = maximum; + } + else + { + if (minimum > std::numeric_limits<int>::min()) + output["minimum"] = (int)minimum; + if (maximum < std::numeric_limits<int>::max()) + output["maximum"] = (int)maximum; + } + + if (exclusiveMinimum) + output["exclusiveMinimum"] = true; + if (exclusiveMaximum) + output["exclusiveMaximum"] = true; + if (divisibleBy > 0) + output["divisibleBy"] = divisibleBy; + } + if (CJSONUtils::HasType(type, StringValue)) + { + if (minLength >= 0) + output["minLength"] = minLength; + if (maxLength >= 0) + output["maxLength"] = maxLength; + } + + // Print array fields + if (CJSONUtils::HasType(type, ArrayValue)) + { + if (items.size() == 1) + { + items.at(0)->Print(false, false, false, printDescriptions, output["items"]); + } + else if (items.size() > 1) + { + output["items"] = CVariant(CVariant::VariantTypeArray); + for (unsigned int itemIndex = 0; itemIndex < items.size(); itemIndex++) + { + CVariant item = CVariant(CVariant::VariantTypeObject); + items.at(itemIndex)->Print(false, false, false, printDescriptions, item); + output["items"].append(item); + } + } + + if (minItems > 0) + output["minItems"] = minItems; + if (maxItems > 0) + output["maxItems"] = maxItems; + + if (additionalItems.size() == 1) + { + additionalItems.at(0)->Print(false, false, false, printDescriptions, output["additionalItems"]); + } + else if (additionalItems.size() > 1) + { + output["additionalItems"] = CVariant(CVariant::VariantTypeArray); + for (unsigned int addItemIndex = 0; addItemIndex < additionalItems.size(); addItemIndex++) + { + CVariant item = CVariant(CVariant::VariantTypeObject); + additionalItems.at(addItemIndex)->Print(false, false, false, printDescriptions, item); + output["additionalItems"].append(item); + } + } + + if (uniqueItems) + output["uniqueItems"] = true; + } + + // Print object fields + if (CJSONUtils::HasType(type, ObjectValue)) + { + if (properties.size() > 0) + { + output["properties"] = CVariant(CVariant::VariantTypeObject); + + JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesEnd = properties.end(); + JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesIterator; + for (propertiesIterator = properties.begin(); propertiesIterator != propertiesEnd; ++propertiesIterator) + { + propertiesIterator->second->Print(false, false, true, printDescriptions, output["properties"][propertiesIterator->first]); + } + } + + if (!hasAdditionalProperties) + output["additionalProperties"] = false; + else if (additionalProperties != NULL && additionalProperties->type != AnyValue) + additionalProperties->Print(false, false, true, printDescriptions, output["additionalProperties"]); + } + } +} + +void JSONSchemaTypeDefinition::ResolveReference() +{ + // Check and set the reference type before recursing + // to guard against cycles + if (referencedTypeSet) + return; + + referencedTypeSet = true; + + // Take care of all nested types + for (const auto& it : extends) + it->ResolveReference(); + for (const auto& it : unionTypes) + it->ResolveReference(); + for (const auto& it : items) + it->ResolveReference(); + for (const auto& it : additionalItems) + it->ResolveReference(); + for (const auto& it : properties) + it.second->ResolveReference(); + + if (additionalProperties) + additionalProperties->ResolveReference(); + + if (referencedType == nullptr) + return; + + std::string origName = name; + std::string origDescription = description; + bool origOptional = optional; + CVariant origDefaultValue = defaultValue; + JSONSchemaTypeDefinitionPtr referencedTypeDef = referencedType; + + // set all the values from the given type definition + *this = *referencedType; + + // restore the original values + if (!origName.empty()) + name = origName; + + if (!origDescription.empty()) + description = origDescription; + + if (!origOptional) + optional = origOptional; + + if (!origDefaultValue.isNull()) + defaultValue = origDefaultValue; + + if (referencedTypeDef.get() != NULL) + referencedType = referencedTypeDef; + + // This will have been overwritten by the copy of the reference + // type so we need to set it again + referencedTypeSet = true; +} + +JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::CJsonSchemaPropertiesMap() : + m_propertiesmap(std::map<std::string, JSONSchemaTypeDefinitionPtr>()) +{ +} + +void JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::add( + const JSONSchemaTypeDefinitionPtr& property) +{ + std::string name = property->name; + StringUtils::ToLower(name); + m_propertiesmap[name] = property; +} + +JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::begin() const +{ + return m_propertiesmap.begin(); +} + +JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::find(const std::string& key) const +{ + return m_propertiesmap.find(key); +} + +JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::end() const +{ + return m_propertiesmap.end(); +} + +unsigned int JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::size() const +{ + return static_cast<unsigned int>(m_propertiesmap.size()); +} + +JsonRpcMethod::JsonRpcMethod() + : missingReference(), + name(), + method(NULL), + description(), + parameters(), + returns(new JSONSchemaTypeDefinition()) +{ } + +bool JsonRpcMethod::Parse(const CVariant &value) +{ + // Parse XBMC specific information about the method + if (value.isMember("transport") && value["transport"].isArray()) + { + int transport = 0; + for (unsigned int index = 0; index < value["transport"].size(); index++) + transport |= StringToTransportLayer(value["transport"][index].asString()); + + transportneed = (TransportLayerCapability)transport; + } + else + transportneed = StringToTransportLayer(value.isMember("transport") ? value["transport"].asString() : ""); + + if (value.isMember("permission") && value["permission"].isArray()) + { + int permissions = 0; + for (unsigned int index = 0; index < value["permission"].size(); index++) + permissions |= StringToPermission(value["permission"][index].asString()); + + permission = (OperationPermission)permissions; + } + else + permission = StringToPermission(value.isMember("permission") ? value["permission"].asString() : ""); + + description = GetString(value["description"], ""); + + // Check whether there are parameters defined + if (value.isMember("params") && value["params"].isArray()) + { + // Loop through all defined parameters + for (unsigned int paramIndex = 0; paramIndex < value["params"].size(); paramIndex++) + { + CVariant parameter = value["params"][paramIndex]; + // If the parameter definition does not contain a valid "name" or + // "type" element we will ignore it + if (!parameter.isMember("name") || !parameter["name"].isString() || + (!parameter.isMember("type") && !parameter.isMember("$ref") && !parameter.isMember("extends")) || + (parameter.isMember("type") && !parameter["type"].isString() && !parameter["type"].isArray()) || + (parameter.isMember("$ref") && !parameter["$ref"].isString()) || + (parameter.isMember("extends") && !parameter["extends"].isString() && !parameter["extends"].isArray())) + { + CLog::Log(LOGDEBUG, "JSONRPC: Method {} has a badly defined parameter", name); + return false; + } + + // Parse the parameter and add it to the list + // of defined parameters + JSONSchemaTypeDefinitionPtr param = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + if (!parseParameter(parameter, param)) + { + missingReference = param->missingReference; + return false; + } + parameters.push_back(param); + } + } + + // Parse the return value of the method + if (!parseReturn(value)) + { + missingReference = returns->missingReference; + return false; + } + + return true; +} + +JSONRPC_STATUS JsonRpcMethod::Check(const CVariant &requestParameters, ITransportLayer *transport, IClient *client, bool notification, MethodCall &methodCall, CVariant &outputParameters) const +{ + if (transport != NULL && (transport->GetCapabilities() & transportneed) == transportneed) + { + if (client != NULL && (client->GetPermissionFlags() & permission) == permission && (!notification || (permission & OPERATION_PERMISSION_NOTIFICATION) == permission)) + { + methodCall = method; + + // Count the number of actually handled (present) + // parameters + unsigned int handled = 0; + CVariant errorData = CVariant(CVariant::VariantTypeObject); + errorData["method"] = name; + + // Loop through all the parameters to check + for (unsigned int i = 0; i < parameters.size(); i++) + { + // Evaluate the current parameter + JSONRPC_STATUS status = checkParameter(requestParameters, parameters.at(i), i, outputParameters, handled, errorData); + if (status != OK) + { + // Return the error data object in the outputParameters reference + outputParameters = errorData; + return status; + } + } + + // Check if there were unnecessary parameters + if (handled < requestParameters.size()) + { + errorData["message"] = "Too many parameters"; + outputParameters = errorData; + return InvalidParams; + } + + return OK; + } + else + return BadPermission; + } + + return MethodNotFound; +} + +bool JsonRpcMethod::parseParameter(const CVariant& value, + const JSONSchemaTypeDefinitionPtr& parameter) +{ + parameter->name = GetString(value["name"], ""); + + // Parse the type and default value of the parameter + return parameter->Parse(value, true); +} + +bool JsonRpcMethod::parseReturn(const CVariant &value) +{ + // Only parse the "returns" definition if there is one + if (!value.isMember("returns")) + { + returns->type = NullValue; + return true; + } + + // If the type of the return value is defined as a simple string we can parse it directly + if (value["returns"].isString()) + return CJSONServiceDescription::parseJSONSchemaType(value["returns"], returns->unionTypes, returns->type, missingReference); + + // otherwise we have to parse the whole type definition + if (!returns->Parse(value["returns"])) + { + missingReference = returns->missingReference; + return false; + } + + return true; +} + +JSONRPC_STATUS JsonRpcMethod::checkParameter(const CVariant& requestParameters, + const JSONSchemaTypeDefinitionPtr& type, + unsigned int position, + CVariant& outputParameters, + unsigned int& handled, + CVariant& errorData) +{ + // Let's check if the parameter has been provided + if (ParameterExists(requestParameters, type->name, position)) + { + // Get the parameter + CVariant parameterValue = GetParameter(requestParameters, type->name, position); + + // Evaluate the type of the parameter + JSONRPC_STATUS status = type->Check(parameterValue, outputParameters[type->name], errorData["stack"]); + if (status != OK) + return status; + + // The parameter was present and valid + handled++; + } + // If the parameter has not been provided but is optional + // we can use its default value + else if (type->optional) + outputParameters[type->name] = type->defaultValue; + // The parameter is required but has not been provided => invalid + else + { + errorData["stack"]["name"] = type->name; + SchemaValueTypeToJson(type->type, errorData["stack"]["type"]); + errorData["stack"]["message"] = "Missing parameter"; + return InvalidParams; + } + + return OK; +} + +void CJSONServiceDescription::ResolveReferences() +{ + for (const auto& it : m_types) + it.second->ResolveReference(); +} + +void CJSONServiceDescription::Cleanup() +{ + // reset all of the static data + m_notifications.clear(); + m_actionMap.clear(); + m_types.clear(); + m_incompleteDefinitions.clear(); +} + +bool CJSONServiceDescription::prepareDescription(std::string &description, CVariant &descriptionObject, std::string &name) +{ + if (description.empty()) + { + CLog::Log(LOGERROR, "JSONRPC: Missing JSON Schema definition for \"{}\"", name); + return false; + } + + if (description.at(0) != '{') + description = StringUtils::Format("{{{:s}}}", description); + + // Make sure the method description actually exists and represents an object + if (!CJSONVariantParser::Parse(description, descriptionObject) || !descriptionObject.isObject()) + { + CLog::Log(LOGERROR, "JSONRPC: Unable to parse JSON Schema definition for \"{}\"", name); + return false; + } + + CVariant::const_iterator_map member = descriptionObject.begin_map(); + if (member != descriptionObject.end_map()) + name = member->first; + + if (name.empty() || + (!descriptionObject[name].isMember("type") && !descriptionObject[name].isMember("$ref") && !descriptionObject[name].isMember("extends"))) + { + CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for \"{}\"", name); + return false; + } + + return true; +} + +bool CJSONServiceDescription::addMethod(const std::string &jsonMethod, MethodCall method) +{ + CVariant descriptionObject; + std::string methodName; + + std::string modJsonMethod = jsonMethod; + // Make sure the method description actually exists and represents an object + if (!prepareDescription(modJsonMethod, descriptionObject, methodName)) + { + CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for method \"{}\"", methodName); + return false; + } + + if (m_actionMap.find(methodName) != m_actionMap.end()) + { + CLog::Log(LOGERROR, "JSONRPC: There already is a method with the name \"{}\"", methodName); + return false; + } + + std::string type = GetString(descriptionObject[methodName]["type"], ""); + if (type.compare("method") != 0) + { + CLog::Log(LOGERROR, "JSONRPC: Invalid JSON type for method \"{}\"", methodName); + return false; + } + + if (method == NULL) + { + unsigned int size = sizeof(m_methodMaps) / sizeof(JsonRpcMethodMap); + for (unsigned int index = 0; index < size; index++) + { + if (methodName.compare(m_methodMaps[index].name) == 0) + { + method = m_methodMaps[index].method; + break; + } + } + + if (method == NULL) + { + CLog::Log(LOGERROR, "JSONRPC: Missing implementation for method \"{}\"", methodName); + return false; + } + } + + // Parse the details of the method + JsonRpcMethod newMethod; + newMethod.name = methodName; + newMethod.method = method; + + if (!newMethod.Parse(descriptionObject[newMethod.name])) + { + CLog::Log(LOGERROR, "JSONRPC: Could not parse method \"{}\"", methodName); + if (!newMethod.missingReference.empty()) + { + IncompleteSchemaDefinition incomplete; + incomplete.Schema = modJsonMethod; + incomplete.Type = SchemaDefinitionMethod; + incomplete.Method = method; + + IncompleteSchemaDefinitionMap::iterator iter = m_incompleteDefinitions.find(newMethod.missingReference); + if (iter == m_incompleteDefinitions.end()) + m_incompleteDefinitions[newMethod.missingReference] = std::vector<IncompleteSchemaDefinition>(); + + CLog::Log( + LOGINFO, + "JSONRPC: Adding method \"{}\" to list of incomplete definitions (waiting for \"{}\")", + methodName, newMethod.missingReference); + m_incompleteDefinitions[newMethod.missingReference].push_back(incomplete); + } + + return false; + } + + m_actionMap.add(newMethod); + + return true; +} + +bool CJSONServiceDescription::AddType(const std::string &jsonType) +{ + CVariant descriptionObject; + std::string typeName; + + std::string modJsonType = jsonType; + if (!prepareDescription(modJsonType, descriptionObject, typeName)) + { + CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for type \"{}\"", typeName); + return false; + } + + if (m_types.find(typeName) != m_types.end()) + { + CLog::Log(LOGERROR, "JSONRPC: There already is a type with the name \"{}\"", typeName); + return false; + } + + // Make sure the "id" attribute is correctly populated + descriptionObject[typeName]["id"] = typeName; + + JSONSchemaTypeDefinitionPtr globalType = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + globalType->name = typeName; + globalType->ID = typeName; + CJSONServiceDescription::addReferenceTypeDefinition(globalType); + + if (!globalType->Parse(descriptionObject[typeName])) + { + CLog::Log(LOGWARNING, "JSONRPC: Could not parse type \"{}\"", typeName); + CJSONServiceDescription::removeReferenceTypeDefinition(typeName); + if (!globalType->missingReference.empty()) + { + IncompleteSchemaDefinition incomplete; + incomplete.Schema = modJsonType; + incomplete.Type = SchemaDefinitionType; + + IncompleteSchemaDefinitionMap::iterator iter = m_incompleteDefinitions.find(globalType->missingReference); + if (iter == m_incompleteDefinitions.end()) + m_incompleteDefinitions[globalType->missingReference] = std::vector<IncompleteSchemaDefinition>(); + + CLog::Log( + LOGINFO, + "JSONRPC: Adding type \"{}\" to list of incomplete definitions (waiting for \"{}\")", + typeName, globalType->missingReference); + m_incompleteDefinitions[globalType->missingReference].push_back(incomplete); + } + + globalType.reset(); + + return false; + } + + return true; +} + +bool CJSONServiceDescription::AddMethod(const std::string &jsonMethod, MethodCall method) +{ + if (method == NULL) + { + CLog::Log(LOGERROR, "JSONRPC: Invalid JSONRPC method implementation"); + return false; + } + + return addMethod(jsonMethod, method); +} + +bool CJSONServiceDescription::AddBuiltinMethod(const std::string &jsonMethod) +{ + return addMethod(jsonMethod, NULL); +} + +bool CJSONServiceDescription::AddNotification(const std::string &jsonNotification) +{ + CVariant descriptionObject; + std::string notificationName; + + std::string modJsonNotification = jsonNotification; + // Make sure the notification description actually exists and represents an object + if (!prepareDescription(modJsonNotification, descriptionObject, notificationName)) + { + CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for notification \"{}\"", + notificationName); + return false; + } + + if (m_notifications.find(notificationName) != m_notifications.end()) + { + CLog::Log(LOGERROR, "JSONRPC: There already is a notification with the name \"{}\"", + notificationName); + return false; + } + + std::string type = GetString(descriptionObject[notificationName]["type"], ""); + if (type.compare("notification") != 0) + { + CLog::Log(LOGERROR, "JSONRPC: Invalid JSON type for notification \"{}\"", notificationName); + return false; + } + + m_notifications[notificationName] = descriptionObject; + + return true; +} + +bool CJSONServiceDescription::AddEnum(const std::string &name, const std::vector<CVariant> &values, CVariant::VariantType type /* = CVariant::VariantTypeNull */, const CVariant &defaultValue /* = CVariant::ConstNullVariant */) +{ + if (name.empty() || m_types.find(name) != m_types.end() || + values.size() == 0) + return false; + + JSONSchemaTypeDefinitionPtr definition = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + definition->ID = name; + + std::vector<CVariant::VariantType> types; + bool autoType = false; + if (type == CVariant::VariantTypeNull) + autoType = true; + else + types.push_back(type); + + for (unsigned int index = 0; index < values.size(); index++) + { + if (autoType) + types.push_back(values[index].type()); + else if (type != CVariant::VariantTypeConstNull && type != values[index].type()) + return false; + } + definition->enums.insert(definition->enums.begin(), values.begin(), values.end()); + + int schemaType = (int)AnyValue; + for (unsigned int index = 0; index < types.size(); index++) + { + JSONSchemaType currentType; + switch (type) + { + case CVariant::VariantTypeString: + currentType = StringValue; + break; + case CVariant::VariantTypeDouble: + currentType = NumberValue; + break; + case CVariant::VariantTypeInteger: + case CVariant::VariantTypeUnsignedInteger: + currentType = IntegerValue; + break; + case CVariant::VariantTypeBoolean: + currentType = BooleanValue; + break; + case CVariant::VariantTypeArray: + currentType = ArrayValue; + break; + case CVariant::VariantTypeObject: + currentType = ObjectValue; + break; + case CVariant::VariantTypeConstNull: + currentType = AnyValue; + break; + default: + case CVariant::VariantTypeNull: + return false; + } + + if (index == 0) + schemaType = currentType; + else + schemaType |= (int)currentType; + } + definition->type = (JSONSchemaType)schemaType; + + if (defaultValue.type() == CVariant::VariantTypeConstNull) + definition->defaultValue = definition->enums.at(0); + else + definition->defaultValue = defaultValue; + + addReferenceTypeDefinition(definition); + + return true; +} + +bool CJSONServiceDescription::AddEnum(const std::string &name, const std::vector<std::string> &values) +{ + std::vector<CVariant> enums; + enums.reserve(values.size()); + for (const auto& it : values) + enums.emplace_back(it); + + return AddEnum(name, enums, CVariant::VariantTypeString); +} + +bool CJSONServiceDescription::AddEnum(const std::string &name, const std::vector<int> &values) +{ + std::vector<CVariant> enums; + enums.reserve(values.size()); + for (const auto& it : values) + enums.emplace_back(it); + + return AddEnum(name, enums, CVariant::VariantTypeInteger); +} + +const char* CJSONServiceDescription::GetVersion() +{ + return JSONRPC_SERVICE_VERSION; +} + +JSONRPC_STATUS CJSONServiceDescription::Print(CVariant &result, ITransportLayer *transport, IClient *client, + bool printDescriptions /* = true */, bool printMetadata /* = false */, bool filterByTransport /* = true */, + const std::string &filterByName /* = "" */, const std::string &filterByType /* = "" */, bool printReferences /* = true */) +{ + std::map<std::string, JSONSchemaTypeDefinitionPtr> types; + CJsonRpcMethodMap methods; + std::map<std::string, CVariant> notifications; + + int clientPermissions = client->GetPermissionFlags(); + int transportCapabilities = transport->GetCapabilities(); + + if (filterByName.size() > 0) + { + std::string name = filterByName; + + if (filterByType == "method") + { + StringUtils::ToLower(name); + + CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator = m_actionMap.find(name); + if (methodIterator != m_actionMap.end() && + (clientPermissions & methodIterator->second.permission) == methodIterator->second.permission && ((transportCapabilities & methodIterator->second.transportneed) == methodIterator->second.transportneed || !filterByTransport)) + methods.add(methodIterator->second); + else + return InvalidParams; + } + else if (filterByType == "namespace") + { + // append a . delimiter to make sure we check for a namespace + StringUtils::ToLower(name); + name.append("."); + + CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator; + CJsonRpcMethodMap::JsonRpcMethodIterator methodIteratorEnd = m_actionMap.end(); + for (methodIterator = m_actionMap.begin(); methodIterator != methodIteratorEnd; methodIterator++) + { + // Check if the given name is at the very beginning of the method name + if (methodIterator->first.find(name) == 0 && + (clientPermissions & methodIterator->second.permission) == methodIterator->second.permission && ((transportCapabilities & methodIterator->second.transportneed) == methodIterator->second.transportneed || !filterByTransport)) + methods.add(methodIterator->second); + } + + if (methods.begin() == methods.end()) + return InvalidParams; + } + else if (filterByType == "type") + { + std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator typeIterator = m_types.find(name); + if (typeIterator != m_types.end()) + types[typeIterator->first] = typeIterator->second; + else + return InvalidParams; + } + else if (filterByType == "notification") + { + std::map<std::string, CVariant>::const_iterator notificationIterator = m_notifications.find(name); + if (notificationIterator != m_notifications.end()) + notifications[notificationIterator->first] = notificationIterator->second; + else + return InvalidParams; + } + else + return InvalidParams; + + // If we need to print all referenced types we have to go through all parameters etc + if (printReferences) + { + std::vector<std::string> referencedTypes; + + // Loop through all printed types to get all referenced types + std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator typeIterator; + std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator typeIteratorEnd = types.end(); + for (typeIterator = types.begin(); typeIterator != typeIteratorEnd; ++typeIterator) + getReferencedTypes(typeIterator->second, referencedTypes); + + // Loop through all printed method's parameters and return value to get all referenced types + CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator; + CJsonRpcMethodMap::JsonRpcMethodIterator methodIteratorEnd = methods.end(); + for (methodIterator = methods.begin(); methodIterator != methodIteratorEnd; methodIterator++) + { + for (unsigned int index = 0; index < methodIterator->second.parameters.size(); index++) + getReferencedTypes(methodIterator->second.parameters.at(index), referencedTypes); + + getReferencedTypes(methodIterator->second.returns, referencedTypes); + } + + for (unsigned int index = 0; index < referencedTypes.size(); index++) + { + std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator typeIterator = m_types.find(referencedTypes.at(index)); + if (typeIterator != m_types.end()) + types[typeIterator->first] = typeIterator->second; + } + } + } + else + { + types = m_types; + methods = m_actionMap; + notifications = m_notifications; + } + + // Print the header + result["id"] = JSONRPC_SERVICE_ID; + result["version"] = JSONRPC_SERVICE_VERSION; + result["description"] = JSONRPC_SERVICE_DESCRIPTION; + + std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator typeIterator; + std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator typeIteratorEnd = types.end(); + for (typeIterator = types.begin(); typeIterator != typeIteratorEnd; ++typeIterator) + { + CVariant currentType = CVariant(CVariant::VariantTypeObject); + typeIterator->second->Print(false, true, true, printDescriptions, currentType); + + result["types"][typeIterator->first] = currentType; + } + + // Iterate through all json rpc methods + CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator; + CJsonRpcMethodMap::JsonRpcMethodIterator methodIteratorEnd = methods.end(); + for (methodIterator = methods.begin(); methodIterator != methodIteratorEnd; methodIterator++) + { + if ((clientPermissions & methodIterator->second.permission) != methodIterator->second.permission || ((transportCapabilities & methodIterator->second.transportneed) != methodIterator->second.transportneed && filterByTransport)) + continue; + + CVariant currentMethod = CVariant(CVariant::VariantTypeObject); + + currentMethod["type"] = "method"; + if (printDescriptions && !methodIterator->second.description.empty()) + currentMethod["description"] = methodIterator->second.description; + if (printMetadata) + { + CVariant permissions(CVariant::VariantTypeArray); + for (int i = ReadData; i <= OPERATION_PERMISSION_ALL; i *= 2) + { + if ((methodIterator->second.permission & i) == i) + permissions.push_back(PermissionToString((OperationPermission)i)); + } + + if (permissions.size() == 1) + currentMethod["permission"] = permissions[0]; + else + currentMethod["permission"] = permissions; + } + + currentMethod["params"] = CVariant(CVariant::VariantTypeArray); + for (unsigned int paramIndex = 0; paramIndex < methodIterator->second.parameters.size(); paramIndex++) + { + CVariant param = CVariant(CVariant::VariantTypeObject); + methodIterator->second.parameters.at(paramIndex)->Print(true, false, true, printDescriptions, param); + currentMethod["params"].append(param); + } + + methodIterator->second.returns->Print(false, false, false, printDescriptions, currentMethod["returns"]); + + result["methods"][methodIterator->second.name] = currentMethod; + } + + // Print notification description + std::map<std::string, CVariant>::const_iterator notificationIterator; + std::map<std::string, CVariant>::const_iterator notificationIteratorEnd = notifications.end(); + for (notificationIterator = notifications.begin(); notificationIterator != notificationIteratorEnd; ++notificationIterator) + result["notifications"][notificationIterator->first] = notificationIterator->second[notificationIterator->first]; + + return OK; +} + +JSONRPC_STATUS CJSONServiceDescription::CheckCall(const char* const method, const CVariant &requestParameters, ITransportLayer *transport, IClient *client, bool notification, MethodCall &methodCall, CVariant &outputParameters) +{ + CJsonRpcMethodMap::JsonRpcMethodIterator iter = m_actionMap.find(method); + if (iter != m_actionMap.end()) + return iter->second.Check(requestParameters, transport, client, notification, methodCall, outputParameters); + + return MethodNotFound; +} + +JSONSchemaTypeDefinitionPtr CJSONServiceDescription::GetType(const std::string &identification) +{ + std::map<std::string, JSONSchemaTypeDefinitionPtr>::iterator iter = m_types.find(identification); + if (iter == m_types.end()) + return JSONSchemaTypeDefinitionPtr(); + + return iter->second; +} + +bool CJSONServiceDescription::parseJSONSchemaType(const CVariant &value, std::vector<JSONSchemaTypeDefinitionPtr>& typeDefinitions, JSONSchemaType &schemaType, std::string &missingReference) +{ + missingReference.clear(); + schemaType = AnyValue; + + if (value.isArray()) + { + int parsedType = 0; + // If the defined type is an array, we have + // to handle a union type + for (unsigned int typeIndex = 0; typeIndex < value.size(); typeIndex++) + { + JSONSchemaTypeDefinitionPtr definition = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + // If the type is a string try to parse it + if (value[typeIndex].isString()) + definition->type = StringToSchemaValueType(value[typeIndex].asString()); + else if (value[typeIndex].isObject()) + { + if (!definition->Parse(value[typeIndex])) + { + missingReference = definition->missingReference; + CLog::Log(LOGERROR, "JSONRPC: Invalid type schema in union type definition"); + return false; + } + } + else + { + CLog::Log(LOGWARNING, "JSONRPC: Invalid type in union type definition"); + return false; + } + + definition->optional = false; + typeDefinitions.push_back(definition); + parsedType |= definition->type; + } + + // If the type has not been set yet set it to "any" + if (parsedType != 0) + schemaType = (JSONSchemaType)parsedType; + + return true; + } + + if (value.isString()) + { + schemaType = StringToSchemaValueType(value.asString()); + return true; + } + + return false; +} + +void CJSONServiceDescription::addReferenceTypeDefinition( + const JSONSchemaTypeDefinitionPtr& typeDefinition) +{ + // If the given json value is no object or does not contain an "id" field + // of type string it is no valid type definition + if (typeDefinition->ID.empty()) + return; + + // If the id has already been defined we ignore the type definition + if (m_types.find(typeDefinition->ID) != m_types.end()) + return; + + // Add the type to the list of type definitions + m_types[typeDefinition->ID] = typeDefinition; + + IncompleteSchemaDefinitionMap::iterator iter = m_incompleteDefinitions.find(typeDefinition->ID); + if (iter == m_incompleteDefinitions.end()) + return; + + CLog::Log(LOGINFO, "JSONRPC: Resolving incomplete types/methods referencing {}", + typeDefinition->ID); + for (unsigned int index = 0; index < iter->second.size(); index++) + { + if (iter->second[index].Type == SchemaDefinitionType) + AddType(iter->second[index].Schema); + else + AddMethod(iter->second[index].Schema, iter->second[index].Method); + } + + m_incompleteDefinitions.erase(typeDefinition->ID); +} + +void CJSONServiceDescription::removeReferenceTypeDefinition(const std::string &typeID) +{ + if (typeID.empty()) + return; + + std::map<std::string, JSONSchemaTypeDefinitionPtr>::iterator type = m_types.find(typeID); + if (type != m_types.end()) + m_types.erase(type); +} + +void CJSONServiceDescription::getReferencedTypes(const JSONSchemaTypeDefinitionPtr& type, + std::vector<std::string>& referencedTypes) +{ + // If the current type is a referenceable object, we can add it to the list + if (type->ID.size() > 0) + { + for (unsigned int index = 0; index < referencedTypes.size(); index++) + { + // The referenceable object has already been added to the list so we can just skip it + if (type->ID == referencedTypes.at(index)) + return; + } + + referencedTypes.push_back(type->ID); + } + + // If the current type is an object we need to check its properties + if (HasType(type->type, ObjectValue)) + { + JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator iter; + JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator iterEnd = type->properties.end(); + for (iter = type->properties.begin(); iter != iterEnd; ++iter) + getReferencedTypes(iter->second, referencedTypes); + } + // If the current type is an array we need to check its items + if (HasType(type->type, ArrayValue)) + { + unsigned int index; + for (index = 0; index < type->items.size(); index++) + getReferencedTypes(type->items.at(index), referencedTypes); + + for (index = 0; index < type->additionalItems.size(); index++) + getReferencedTypes(type->additionalItems.at(index), referencedTypes); + } + + // If the current type extends others type we need to check those types + for (unsigned int index = 0; index < type->extends.size(); index++) + getReferencedTypes(type->extends.at(index), referencedTypes); + + // If the current type is a union type we need to check those types + for (unsigned int index = 0; index < type->unionTypes.size(); index++) + getReferencedTypes(type->unionTypes.at(index), referencedTypes); +} + +CJSONServiceDescription::CJsonRpcMethodMap::CJsonRpcMethodMap(): + m_actionmap(std::map<std::string, JsonRpcMethod>()) +{ +} + +void CJSONServiceDescription::CJsonRpcMethodMap::clear() +{ + m_actionmap.clear(); +} + +void CJSONServiceDescription::CJsonRpcMethodMap::add(const JsonRpcMethod &method) +{ + std::string name = method.name; + StringUtils::ToLower(name); + m_actionmap[name] = method; +} + +CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::begin() const +{ + return m_actionmap.begin(); +} + +CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::find(const std::string& key) const +{ + return m_actionmap.find(key); +} + +CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::end() const +{ + return m_actionmap.end(); +} diff --git a/xbmc/interfaces/json-rpc/JSONServiceDescription.h b/xbmc/interfaces/json-rpc/JSONServiceDescription.h new file mode 100644 index 0000000..ee48920 --- /dev/null +++ b/xbmc/interfaces/json-rpc/JSONServiceDescription.h @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONUtils.h" +#include "utils/Variant.h" + +#include <limits> +#include <memory> +#include <string> +#include <vector> + +namespace JSONRPC +{ + class JSONSchemaTypeDefinition; + typedef std::shared_ptr<JSONSchemaTypeDefinition> JSONSchemaTypeDefinitionPtr; + + /*! + \ingroup jsonrpc + \brief Class for a parameter of a + json rpc method. + + Represents a parameter of a defined + json rpc method and is used to verify + and extract the value of the parameter + in a method call. + */ + class JSONSchemaTypeDefinition : protected CJSONUtils + { + public: + JSONSchemaTypeDefinition(); + + bool Parse(const CVariant &value, bool isParameter = false); + JSONRPC_STATUS Check(const CVariant& value, CVariant& outputValue, CVariant& errorData) const; + void Print(bool isParameter, bool isGlobal, bool printDefault, bool printDescriptions, CVariant &output) const; + void ResolveReference(); + + std::string missingReference; + + /*! + \brief Name of the parameter (for + by-name calls) + */ + std::string name; + + /*! + \brief Id of the type (for + referenced types) + Renamed from "id" because of possible + issues with Objective-C. + */ + std::string ID; + + /*! + \brief Referenced object + */ + JSONSchemaTypeDefinitionPtr referencedType; + + /*! + \brief Whether the type has been set + based on the referenced type + */ + bool referencedTypeSet = false; + + /*! + \brief Array of reference types + which are extended by this type. + */ + std::vector<JSONSchemaTypeDefinitionPtr> extends; + + /*! + \brief Description of the parameter + */ + std::string description; + + /*! + \brief JSON schema type of the parameter's value + */ + JSONSchemaType type = AnyValue; + + /*! + \brief JSON schema type definitions in case + of a union type + */ + std::vector<JSONSchemaTypeDefinitionPtr> unionTypes; + + /*! + \brief Whether or not the parameter is + optional + */ + bool optional = true; + + /*! + \brief Default value of the parameter + (only needed when it is optional) + */ + CVariant defaultValue; + + /*! + \brief Minimum value for Integer + or Number types + */ + double minimum; + + /*! + \brief Maximum value for Integer or Number types + */ + double maximum; + + /*! + \brief Whether to exclude the defined Minimum + value from the valid range or not + */ + bool exclusiveMinimum = false; + + /*! + \brief Whether to exclude the defined Maximum + value from the valid range or not + */ + bool exclusiveMaximum = false; + + /*! + \brief Integer by which the value (of type + Integer) must be divisible without rest + */ + unsigned int divisibleBy = 0; + + /*! + \brief Minimum length for String types + */ + int minLength = -1; + + /*! + \brief Maximum length for String types + */ + int maxLength = -1; + + /*! + \brief (Optional) List of allowed values + for the type + */ + std::vector<CVariant> enums; + + /*! + \brief List of possible values in an array + */ + std::vector<JSONSchemaTypeDefinitionPtr> items; + + /*! + \brief Minimum amount of items in the array + */ + unsigned int minItems = 0; + + /*! + \brief Maximum amount of items in the array + */ + unsigned int maxItems = 0; + + /*! + \brief Whether every value in the array + must be unique or not + */ + bool uniqueItems = false; + + /*! + \brief List of json schema definitions for + additional items in an array with tuple + typing (defined schemas in "items") + */ + std::vector<JSONSchemaTypeDefinitionPtr> additionalItems; + + /*! + \brief Maps a properties name to its + json schema type definition + */ + class CJsonSchemaPropertiesMap + { + public: + CJsonSchemaPropertiesMap(); + + void add(const JSONSchemaTypeDefinitionPtr& property); + + typedef std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator JSONSchemaPropertiesIterator; + JSONSchemaPropertiesIterator begin() const; + JSONSchemaPropertiesIterator find(const std::string& key) const; + JSONSchemaPropertiesIterator end() const; + unsigned int size() const; + private: + std::map<std::string, JSONSchemaTypeDefinitionPtr> m_propertiesmap; + }; + + /*! + \brief List of properties of the parameter (only needed when the + parameter is an object) + */ + CJsonSchemaPropertiesMap properties; + + /*! + \brief Whether the type can have additional properties + or not + */ + bool hasAdditionalProperties = false; + + /*! + \brief Type definition for additional properties + */ + JSONSchemaTypeDefinitionPtr additionalProperties; + }; + + /*! + \ingroup jsonrpc + \brief Structure for a published json + rpc method. + + Represents a published json rpc method + and is used to verify an incoming json + rpc request against a defined method. + */ + class JsonRpcMethod : protected CJSONUtils + { + public: + JsonRpcMethod(); + + bool Parse(const CVariant &value); + JSONRPC_STATUS Check(const CVariant &requestParameters, ITransportLayer *transport, IClient *client, bool notification, MethodCall &methodCall, CVariant &outputParameters) const; + + std::string missingReference; + + /*! + \brief Name of the represented method + */ + std::string name; + /*! + \brief Pointer tot he implementation + of the represented method + */ + MethodCall method; + /*! + \brief Definition of the type of + request/response + */ + TransportLayerCapability transportneed = Response; + /*! + \brief Definition of the permissions needed + to execute the method + */ + OperationPermission permission = ReadData; + /*! + \brief Description of the method + */ + std::string description; + /*! + \brief List of accepted parameters + */ + std::vector<JSONSchemaTypeDefinitionPtr> parameters; + /*! + \brief Definition of the return value + */ + JSONSchemaTypeDefinitionPtr returns; + + private: + bool parseParameter(const CVariant& value, const JSONSchemaTypeDefinitionPtr& parameter); + bool parseReturn(const CVariant &value); + static JSONRPC_STATUS checkParameter(const CVariant& requestParameters, + const JSONSchemaTypeDefinitionPtr& type, + unsigned int position, + CVariant& outputParameters, + unsigned int& handled, + CVariant& errorData); + }; + + /*! + \ingroup jsonrpc + \brief Structure mapping a json rpc method + definition to an actual method implementation. + */ + typedef struct + { + /*! + \brief Name of the json rpc method. + */ + std::string name; + /*! + \brief Pointer to the actual + implementation of the json rpc + method. + */ + MethodCall method; + } JsonRpcMethodMap; + + /*! + \ingroup jsonrpc + \brief Helper class for json schema service descriptor based + service descriptions for the json rpc API + + Provides static functions to parse a complete json schema + service descriptor of a published service containing json rpc + methods, print the json schema service descriptor representation + into a string (mainly for output purposes) and evaluate and verify + parameters provided in a call to one of the publish json rpc methods + against a parameter definition parsed from a json schema service + descriptor. + */ + class CJSONServiceDescription : public CJSONUtils + { + friend class JSONSchemaTypeDefinition; + friend class JsonRpcMethod; + public: + /*! + \brief Parses the given json schema description and evaluates + and stores the defined type + \param jsonType json schema description to parse + \return True if the json schema description has been parsed successfully otherwise false + */ + static bool AddType(const std::string &jsonType); + + /*! + \brief Parses the given json schema description and evaluates + and stores the defined method + \param jsonMethod json schema description to parse + \param method pointer to the implementation + \return True if the json schema description has been parsed successfully otherwise false + */ + static bool AddMethod(const std::string &jsonMethod, MethodCall method); + + /*! + \brief Parses the given json schema description and evaluates + and stores the defined builtin method + \param jsonMethod json schema description to parse + \return True if the json schema description has been parsed successfully otherwise false + */ + static bool AddBuiltinMethod(const std::string &jsonMethod); + + /*! + \brief Parses the given json schema description and evaluates + and stores the defined notification + \param jsonNotification json schema description to parse + \return True if the json schema description has been parsed successfully otherwise false + */ + static bool AddNotification(const std::string &jsonNotification); + + static bool AddEnum(const std::string &name, const std::vector<CVariant> &values, CVariant::VariantType type = CVariant::VariantTypeNull, const CVariant &defaultValue = CVariant::ConstNullVariant); + static bool AddEnum(const std::string &name, const std::vector<std::string> &values); + static bool AddEnum(const std::string &name, const std::vector<int> &values); + + /*! + \brief Gets the version of the json + schema description + \return Version of the json schema description + */ + static const char* GetVersion(); + + /*! + \brief Prints the json schema description into the given result object + \param result Object into which the json schema description is printed + \param transport Transport layer capabilities + \param client Client requesting a print + \param printDescriptions Whether to print descriptions or not + \param printMetadata Whether to print XBMC specific data or not + \param filterByTransport Whether to filter by transport or not + */ + static JSONRPC_STATUS Print(CVariant &result, ITransportLayer *transport, IClient *client, bool printDescriptions = true, bool printMetadata = false, bool filterByTransport = true, const std::string &filterByName = "", const std::string &filterByType = "", bool printReferences = true); + + /*! + \brief Checks the given parameters from the request against the + json schema description for the given method + \param method Called method + \param requestParameters Parameters from the request + \param client Client who sent the request + \param notification Whether the request was sent as a notification or not + \param methodCall Object which will contain the actual C/C++ method to be called + \param outputParameters Cleaned up parameter list + \return OK if the validation of the request succeeded otherwise an appropriate error code + + Checks if the given method is a valid json rpc method, if the client has the permission + to call this method, if the method can be called as a notification or not, assigns the + actual C/C++ implementation of the method to the "methodCall" parameter and checks the + given parameters from the request against the json schema description for the given method. + */ + static JSONRPC_STATUS CheckCall(const char* method, const CVariant &requestParameters, ITransportLayer *transport, IClient *client, bool notification, MethodCall &methodCall, CVariant &outputParameters); + + static JSONSchemaTypeDefinitionPtr GetType(const std::string &identification); + + static void ResolveReferences(); + static void Cleanup(); + + private: + static bool prepareDescription(std::string &description, CVariant &descriptionObject, std::string &name); + static bool addMethod(const std::string &jsonMethod, MethodCall method); + static void parseHeader(const CVariant &descriptionObject); + static bool parseJSONSchemaType(const CVariant &value, std::vector<JSONSchemaTypeDefinitionPtr>& typeDefinitions, JSONSchemaType &schemaType, std::string &missingReference); + static void addReferenceTypeDefinition(const JSONSchemaTypeDefinitionPtr& typeDefinition); + static void removeReferenceTypeDefinition(const std::string &typeID); + + static void getReferencedTypes(const JSONSchemaTypeDefinitionPtr& type, + std::vector<std::string>& referencedTypes); + + class CJsonRpcMethodMap + { + public: + CJsonRpcMethodMap(); + + void add(const JsonRpcMethod &method); + + typedef std::map<std::string, JsonRpcMethod>::const_iterator JsonRpcMethodIterator; + JsonRpcMethodIterator begin() const; + JsonRpcMethodIterator find(const std::string& key) const; + JsonRpcMethodIterator end() const; + + void clear(); + private: + std::map<std::string, JsonRpcMethod> m_actionmap; + }; + + static CJsonRpcMethodMap m_actionMap; + static std::map<std::string, JSONSchemaTypeDefinitionPtr> m_types; + static std::map<std::string, CVariant> m_notifications; + static JsonRpcMethodMap m_methodMaps[]; + + typedef enum SchemaDefinition + { + SchemaDefinitionType, + SchemaDefinitionMethod + } SchemaDefinition; + + typedef struct IncompleteSchemaDefinition + { + std::string Schema; + SchemaDefinition Type; + MethodCall Method; + } IncompleteSchemaDefinition; + + typedef std::map<std::string, std::vector<IncompleteSchemaDefinition> > IncompleteSchemaDefinitionMap; + static IncompleteSchemaDefinitionMap m_incompleteDefinitions; + }; +} diff --git a/xbmc/interfaces/json-rpc/JSONUtils.cpp b/xbmc/interfaces/json-rpc/JSONUtils.cpp new file mode 100644 index 0000000..a8d60d1 --- /dev/null +++ b/xbmc/interfaces/json-rpc/JSONUtils.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "JSONUtils.h" + +#include "XBDateTime.h" + +namespace JSONRPC +{ + +void CJSONUtils::SetFromDBDate(const CVariant& jsonDate, CDateTime& date) +{ + if (!jsonDate.isString()) + return; + + if (jsonDate.empty()) + date.Reset(); + else + date.SetFromDBDate(jsonDate.asString()); +} + +void CJSONUtils::SetFromDBDateTime(const CVariant& jsonDate, CDateTime& date) +{ + if (!jsonDate.isString()) + return; + + if (jsonDate.empty()) + date.Reset(); + else + date.SetFromDBDateTime(jsonDate.asString()); +} + +} // namespace JSONRPC diff --git a/xbmc/interfaces/json-rpc/JSONUtils.h b/xbmc/interfaces/json-rpc/JSONUtils.h new file mode 100644 index 0000000..150e23b --- /dev/null +++ b/xbmc/interfaces/json-rpc/JSONUtils.h @@ -0,0 +1,490 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPCUtils.h" +#include "playlists/SmartPlayList.h" +#include "utils/JSONVariantParser.h" +#include "utils/JSONVariantWriter.h" +#include "utils/SortUtils.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +#include <stdlib.h> +#include <string.h> +#include <vector> + +class CDateTime; + +namespace JSONRPC +{ + /*! + \brief Possible value types of a parameter or return type + */ + enum JSONSchemaType + { + NullValue = 0x01, + StringValue = 0x02, + NumberValue = 0x04, + IntegerValue = 0x08, + BooleanValue = 0x10, + ArrayValue = 0x20, + ObjectValue = 0x40, + AnyValue = 0x80 + }; + + /*! + \ingroup jsonrpc + \brief Helper class containing utility methods to handle + json rpc method calls.*/ + class CJSONUtils + { + public: + static void MillisecondsToTimeObject(int time, CVariant &result) + { + int ms = time % 1000; + result["milliseconds"] = ms; + time = (time - ms) / 1000; + + int s = time % 60; + result["seconds"] = s; + time = (time - s) / 60; + + int m = time % 60; + result["minutes"] = m; + time = (time -m) / 60; + + result["hours"] = time; + } + + protected: + static void HandleLimits(const CVariant ¶meterObject, CVariant &result, int size, int &start, int &end) + { + if (size < 0) + size = 0; + + start = (int)parameterObject["limits"]["start"].asInteger(); + end = (int)parameterObject["limits"]["end"].asInteger(); + end = (end <= 0 || end > size) ? size : end; + start = start > end ? end : start; + + result["limits"]["start"] = start; + result["limits"]["end"] = end; + result["limits"]["total"] = size; + } + + static bool ParseSorting(const CVariant ¶meterObject, SortBy &sortBy, SortOrder &sortOrder, SortAttribute &sortAttributes) + { + std::string method = parameterObject["sort"]["method"].asString(); + std::string order = parameterObject["sort"]["order"].asString(); + StringUtils::ToLower(method); + StringUtils::ToLower(order); + + // parse the sort attributes + sortAttributes = SortAttributeNone; + if (parameterObject["sort"]["ignorearticle"].asBoolean()) + sortAttributes = static_cast<SortAttribute>(sortAttributes | SortAttributeIgnoreArticle); + if (parameterObject["sort"]["useartistsortname"].asBoolean()) + sortAttributes = static_cast<SortAttribute>(sortAttributes | SortAttributeUseArtistSortName); + + // parse the sort order + sortOrder = SortUtils::SortOrderFromString(order); + if (sortOrder == SortOrderNone) + return false; + + // parse the sort method + sortBy = SortUtils::SortMethodFromString(method); + + return true; + } + + static void ParseLimits(const CVariant ¶meterObject, int &limitStart, int &limitEnd) + { + limitStart = (int)parameterObject["limits"]["start"].asInteger(); + limitEnd = (int)parameterObject["limits"]["end"].asInteger(); + } + + /*! + \brief Checks if the given object contains a parameter + \param parameterObject Object to check for a parameter + \param key Possible name of the parameter + \param position Possible position of the parameter + \return True if the parameter is available otherwise false + + Checks the given object for a parameter with the given key (if + the given object is not an array) or for a parameter at the + given position (if the given object is an array). + */ + static inline bool ParameterExists(const CVariant& parameterObject, + const std::string& key, + unsigned int position) + { + return IsValueMember(parameterObject, key) || + (parameterObject.isArray() && parameterObject.size() > position); + } + + /*! + \brief Checks if the given object contains a value + with the given key + \param value Value to check for the member + \param key Key of the member to check for + \return True if the given object contains a member with + the given key otherwise false + */ + static inline bool IsValueMember(const CVariant& value, const std::string& key) + { + return value.isMember(key); + } + + /*! + \brief Returns the json value of a parameter + \param parameterObject Object containing all provided parameters + \param key Possible name of the parameter + \param position Possible position of the parameter + \return Json value of the parameter with the given name or at the + given position + + Returns the value of the parameter with the given key (if + the given object is not an array) or of the parameter at the + given position (if the given object is an array). + */ + static inline CVariant GetParameter(const CVariant& parameterObject, + const std::string& key, + unsigned int position) + { + return IsValueMember(parameterObject, key) ? parameterObject[key] : parameterObject[position]; + } + + /*! + \brief Returns the json value of a parameter or the given + default value + \param parameterObject Object containing all provided parameters + \param key Possible name of the parameter + \param position Possible position of the parameter + \param fallback Default value of the parameter + \return Json value of the parameter with the given name or at the + given position or the default value if the parameter does not exist + + Returns the value of the parameter with the given key (if + the given object is not an array) or of the parameter at the + given position (if the given object is an array). If the + parameter does not exist the given default value is returned. + */ + static inline CVariant GetParameter(const CVariant& parameterObject, + const std::string& key, + unsigned int position, + const CVariant& fallback) + { + return IsValueMember(parameterObject, key) + ? parameterObject[key] + : ((parameterObject.isArray() && parameterObject.size() > position) + ? parameterObject[position] + : fallback); + } + + /*! + \brief Returns the given json value as a string + \param value Json value to convert to a string + \param defaultValue Default string value + \return String value of the given json value or the default value + if the given json value is no string + */ + static inline std::string GetString(const CVariant &value, const char* defaultValue) + { + std::string str = defaultValue; + if (value.isString()) + { + str = value.asString(); + } + + return str; + } + + /*! + \brief Returns a TransportLayerCapability value of the + given string representation + \param transport String representation of the TransportLayerCapability + \return TransportLayerCapability value of the given string representation + */ + static inline TransportLayerCapability StringToTransportLayer(const std::string& transport) + { + if (transport.compare("Announcing") == 0) + return Announcing; + if (transport.compare("FileDownloadDirect") == 0) + return FileDownloadDirect; + if (transport.compare("FileDownloadRedirect") == 0) + return FileDownloadRedirect; + + return Response; + } + + /*! + \brief Returns a JSONSchemaType value for the given + string representation + \param valueType String representation of the JSONSchemaType + \return JSONSchemaType value of the given string representation + */ + static inline JSONSchemaType StringToSchemaValueType(const std::string& valueType) + { + if (valueType.compare("null") == 0) + return NullValue; + if (valueType.compare("string") == 0) + return StringValue; + if (valueType.compare("number") == 0) + return NumberValue; + if (valueType.compare("integer") == 0) + return IntegerValue; + if (valueType.compare("boolean") == 0) + return BooleanValue; + if (valueType.compare("array") == 0) + return ArrayValue; + if (valueType.compare("object") == 0) + return ObjectValue; + + return AnyValue; + } + + /*! + \brief Returns a string representation for the + given JSONSchemaType + \param valueType Specific JSONSchemaType + \return String representation of the given JSONSchemaType + */ + static inline std::string SchemaValueTypeToString(JSONSchemaType valueType) + { + std::vector<JSONSchemaType> types = std::vector<JSONSchemaType>(); + for (unsigned int value = 0x01; value <= (unsigned int)AnyValue; value *= 2) + { + if (HasType(valueType, (JSONSchemaType)value)) + types.push_back((JSONSchemaType)value); + } + + std::string strType; + if (types.size() > 1) + strType.append("["); + + for (unsigned int index = 0; index < types.size(); index++) + { + if (index > 0) + strType.append(", "); + + switch (types.at(index)) + { + case StringValue: + strType.append("string"); + break; + case NumberValue: + strType.append("number"); + break; + case IntegerValue: + strType.append("integer"); + break; + case BooleanValue: + strType.append("boolean"); + break; + case ArrayValue: + strType.append("array"); + break; + case ObjectValue: + strType.append("object"); + break; + case AnyValue: + strType.append("any"); + break; + case NullValue: + strType.append("null"); + break; + default: + strType.append("unknown"); + } + } + + if (types.size() > 1) + strType.append("]"); + + return strType; + } + + /*! + \brief Converts the given json schema type into + a json object + \param valueTye json schema type(s) + \param jsonObject json object into which the json schema type(s) are stored + */ + static inline void SchemaValueTypeToJson(JSONSchemaType valueType, CVariant &jsonObject) + { + jsonObject = CVariant(CVariant::VariantTypeArray); + for (unsigned int value = 0x01; value <= (unsigned int)AnyValue; value *= 2) + { + if (HasType(valueType, (JSONSchemaType)value)) + jsonObject.append(SchemaValueTypeToString((JSONSchemaType)value)); + } + + if (jsonObject.size() == 1) + { + CVariant jsonType = jsonObject[0]; + jsonObject = jsonType; + } + } + + static inline const char *ValueTypeToString(CVariant::VariantType valueType) + { + switch (valueType) + { + case CVariant::VariantTypeString: + return "string"; + case CVariant::VariantTypeDouble: + return "number"; + case CVariant::VariantTypeInteger: + case CVariant::VariantTypeUnsignedInteger: + return "integer"; + case CVariant::VariantTypeBoolean: + return "boolean"; + case CVariant::VariantTypeArray: + return "array"; + case CVariant::VariantTypeObject: + return "object"; + case CVariant::VariantTypeNull: + case CVariant::VariantTypeConstNull: + return "null"; + default: + return "unknown"; + } + } + + /*! + \brief Checks if the parameter with the given name or at + the given position is of a certain type + \param parameterObject Object containing all provided parameters + \param key Possible name of the parameter + \param position Possible position of the parameter + \param valueType Expected type of the parameter + \return True if the specific parameter is of the given type otherwise false + */ + static inline bool IsParameterType(const CVariant ¶meterObject, const char *key, unsigned int position, JSONSchemaType valueType) + { + if ((valueType & AnyValue) == AnyValue) + return true; + + CVariant parameter; + if (IsValueMember(parameterObject, key)) + parameter = parameterObject[key]; + else if(parameterObject.isArray() && parameterObject.size() > position) + parameter = parameterObject[position]; + + return IsType(parameter, valueType); + } + + /*! + \brief Checks if the given json value is of the given type + \param value Json value to check + \param valueType Expected type of the json value + \return True if the given json value is of the given type otherwise false + */ + static inline bool IsType(const CVariant &value, JSONSchemaType valueType) + { + if (HasType(valueType, AnyValue)) + return true; + if (HasType(valueType, StringValue) && value.isString()) + return true; + if (HasType(valueType, NumberValue) && (value.isInteger() || value.isUnsignedInteger() || value.isDouble())) + return true; + if (HasType(valueType, IntegerValue) && (value.isInteger() || value.isUnsignedInteger())) + return true; + if (HasType(valueType, BooleanValue) && value.isBoolean()) + return true; + if (HasType(valueType, ArrayValue) && value.isArray()) + return true; + if (HasType(valueType, ObjectValue) && value.isObject()) + return true; + + return value.isNull(); + } + + /*! + \brief Sets the value of the given json value to the + default value of the given type + \param value Json value to be set + \param valueType Type of the default value + */ + static inline void SetDefaultValue(CVariant &value, JSONSchemaType valueType) + { + switch (valueType) + { + case StringValue: + value = CVariant(""); + break; + case NumberValue: + value = CVariant(CVariant::VariantTypeDouble); + break; + case IntegerValue: + value = CVariant(CVariant::VariantTypeInteger); + break; + case BooleanValue: + value = CVariant(CVariant::VariantTypeBoolean); + break; + case ArrayValue: + value = CVariant(CVariant::VariantTypeArray); + break; + case ObjectValue: + value = CVariant(CVariant::VariantTypeObject); + break; + default: + value = CVariant(CVariant::VariantTypeNull); + } + } + + static inline bool HasType(JSONSchemaType typeObject, JSONSchemaType type) { return (typeObject & type) == type; } + + static inline bool ParameterNotNull(const CVariant& parameterObject, const std::string& key) + { + return parameterObject.isMember(key) && !parameterObject[key].isNull(); + } + + /*! + \brief Copies the values from the jsonStringArray to the stringArray. + stringArray is cleared. + \param jsonStringArray JSON object representing a string array + \param stringArray String array where the values are copied into (cleared) + */ + static void CopyStringArray(const CVariant &jsonStringArray, std::vector<std::string> &stringArray) + { + if (!jsonStringArray.isArray()) + return; + + stringArray.clear(); + for (CVariant::const_iterator_array it = jsonStringArray.begin_array(); it != jsonStringArray.end_array(); ++it) + stringArray.push_back(it->asString()); + } + + static void SetFromDBDate(const CVariant& jsonDate, CDateTime& date); + + static void SetFromDBDateTime(const CVariant& jsonDate, CDateTime& date); + + static bool GetXspFiltering(const std::string &type, const CVariant &filter, std::string &xsp) + { + if (type.empty() || !filter.isObject()) + return false; + + CVariant xspObj(CVariant::VariantTypeObject); + xspObj["type"] = type; + + if (filter.isMember("field")) + { + xspObj["rules"]["and"] = CVariant(CVariant::VariantTypeArray); + xspObj["rules"]["and"].push_back(filter); + } + else + xspObj["rules"] = filter; + + CSmartPlaylist playlist; + return playlist.Load(xspObj) && playlist.SaveAsJson(xsp, false); + } + }; +} diff --git a/xbmc/interfaces/json-rpc/PVROperations.cpp b/xbmc/interfaces/json-rpc/PVROperations.cpp new file mode 100644 index 0000000..3611ad6 --- /dev/null +++ b/xbmc/interfaces/json-rpc/PVROperations.cpp @@ -0,0 +1,543 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "PVROperations.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "pvr/PVRManager.h" +#include "pvr/PVRPlaybackState.h" +#include "pvr/addons/PVRClients.h" +#include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRChannelGroupMember.h" +#include "pvr/channels/PVRChannelGroups.h" +#include "pvr/channels/PVRChannelGroupsContainer.h" +#include "pvr/epg/Epg.h" +#include "pvr/epg/EpgContainer.h" +#include "pvr/epg/EpgInfoTag.h" +#include "pvr/guilib/PVRGUIActionsChannels.h" +#include "pvr/guilib/PVRGUIActionsTimers.h" +#include "pvr/recordings/PVRRecordings.h" +#include "pvr/timers/PVRTimerInfoTag.h" +#include "pvr/timers/PVRTimers.h" +#include "utils/Variant.h" + +using namespace JSONRPC; +using namespace PVR; +using namespace KODI::MESSAGING; + +JSONRPC_STATUS CPVROperations::GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + CVariant properties = CVariant(CVariant::VariantTypeObject); + for (unsigned int index = 0; index < parameterObject["properties"].size(); index++) + { + std::string propertyName = parameterObject["properties"][index].asString(); + CVariant property; + JSONRPC_STATUS ret; + if ((ret = GetPropertyValue(propertyName, property)) != OK) + return ret; + + properties[propertyName] = property; + } + + result = properties; + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetChannelGroups(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRChannelGroupsContainer> channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + if (!channelGroupContainer) + return FailedToExecute; + + CPVRChannelGroups *channelGroups = channelGroupContainer->Get(parameterObject["channeltype"].asString().compare("radio") == 0); + if (channelGroups == NULL) + return FailedToExecute; + + int start, end; + + std::vector<std::shared_ptr<CPVRChannelGroup>> groupList = channelGroups->GetMembers(true); + HandleLimits(parameterObject, result, groupList.size(), start, end); + for (int index = start; index < end; index++) + FillChannelGroupDetails(groupList.at(index), parameterObject, result["channelgroups"], true); + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetChannelGroupDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRChannelGroupsContainer> channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + if (!channelGroupContainer) + return FailedToExecute; + + std::shared_ptr<CPVRChannelGroup> channelGroup; + CVariant id = parameterObject["channelgroupid"]; + if (id.isInteger()) + channelGroup = channelGroupContainer->GetByIdFromAll((int)id.asInteger()); + else if (id.isString()) + channelGroup = channelGroupContainer->GetGroupAll(id.asString() == "allradio"); + + if (channelGroup == NULL) + return InvalidParams; + + FillChannelGroupDetails(channelGroup, parameterObject, result["channelgroupdetails"], false); + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetChannels(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRChannelGroupsContainer> channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + if (!channelGroupContainer) + return FailedToExecute; + + std::shared_ptr<CPVRChannelGroup> channelGroup; + CVariant id = parameterObject["channelgroupid"]; + if (id.isInteger()) + channelGroup = channelGroupContainer->GetByIdFromAll((int)id.asInteger()); + else if (id.isString()) + channelGroup = channelGroupContainer->GetGroupAll(id.asString() == "allradio"); + + if (channelGroup == NULL) + return InvalidParams; + + CFileItemList channels; + const auto groupMembers = channelGroup->GetMembers(CPVRChannelGroup::Include::ONLY_VISIBLE); + for (const auto& groupMember : groupMembers) + { + channels.Add(std::make_shared<CFileItem>(groupMember)); + } + + HandleFileItemList("channelid", false, "channels", channels, parameterObject, result, true); + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetChannelDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRChannelGroupsContainer> channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + if (!channelGroupContainer) + return FailedToExecute; + + std::shared_ptr<CPVRChannel> channel = channelGroupContainer->GetChannelById( + static_cast<int>(parameterObject["channelid"].asInteger())); + if (channel == NULL) + return InvalidParams; + + const std::shared_ptr<CPVRChannelGroupMember> groupMember = + CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetChannelGroupMember(channel); + if (!groupMember) + return InvalidParams; + + HandleFileItem("channelid", false, "channeldetails", std::make_shared<CFileItem>(groupMember), + parameterObject, parameterObject["properties"], result, false); + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetClients(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + int start, end; + + auto clientInfos = CServiceBroker::GetPVRManager().Clients()->GetEnabledClientInfos(); + HandleLimits(parameterObject, result, clientInfos.size(), start, end); + for (int index = start; index < end; index++) + result["clients"].append(clientInfos[index]); + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetBroadcasts(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRChannelGroupsContainer> channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + if (!channelGroupContainer) + return FailedToExecute; + + std::shared_ptr<CPVRChannel> channel = channelGroupContainer->GetChannelById((int)parameterObject["channelid"].asInteger()); + if (channel == NULL) + return InvalidParams; + + std::shared_ptr<CPVREpg> channelEpg = channel->GetEPG(); + if (!channelEpg) + return InternalError; + + CFileItemList programFull; + + const std::vector<std::shared_ptr<CPVREpgInfoTag>> tags = channelEpg->GetTags(); + for (const auto& tag : tags) + { + programFull.Add(std::make_shared<CFileItem>(tag)); + } + + HandleFileItemList("broadcastid", false, "broadcasts", programFull, parameterObject, result, programFull.Size(), true); + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetBroadcastDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + const std::shared_ptr<CPVREpgInfoTag> epgTag = + CServiceBroker::GetPVRManager().EpgContainer().GetTagByDatabaseId( + parameterObject["broadcastid"].asInteger()); + + if (!epgTag) + return InvalidParams; + + HandleFileItem("broadcastid", false, "broadcastdetails", CFileItemPtr(new CFileItem(epgTag)), parameterObject, parameterObject["properties"], result, false); + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetBroadcastIsPlayable(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + const std::shared_ptr<CPVREpgInfoTag> epgTag = + CServiceBroker::GetPVRManager().EpgContainer().GetTagByDatabaseId( + parameterObject["broadcastid"].asInteger()); + + if (!epgTag) + return InvalidParams; + + result = epgTag->IsPlayable(); + + return OK; +} + +JSONRPC_STATUS CPVROperations::Record(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRChannel> pChannel; + CVariant channel = parameterObject["channel"]; + if (channel.isString() && channel.asString() == "current") + { + pChannel = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel(); + if (!pChannel) + return InternalError; + } + else if (channel.isInteger()) + { + std::shared_ptr<CPVRChannelGroupsContainer> channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + if (!channelGroupContainer) + return FailedToExecute; + + pChannel = channelGroupContainer->GetChannelById((int)channel.asInteger()); + } + else + return InvalidParams; + + if (pChannel == NULL) + return InvalidParams; + else if (!pChannel->CanRecord()) + return FailedToExecute; + + CVariant record = parameterObject["record"]; + bool bIsRecording = CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*pChannel); + bool toggle = true; + if (record.isBoolean() && record.asBoolean() == bIsRecording) + toggle = false; + + if (toggle) + { + if (!CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().SetRecordingOnChannel( + pChannel, !bIsRecording)) + return FailedToExecute; + } + + return ACK; +} + +JSONRPC_STATUS CPVROperations::Scan(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + if (parameterObject.isMember("clientid")) + { + if (CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().StartChannelScan( + parameterObject["clientid"].asInteger())) + return ACK; + } + else + { + if (CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().StartChannelScan()) + return ACK; + } + + return FailedToExecute; +} + +JSONRPC_STATUS CPVROperations::GetPropertyValue(const std::string &property, CVariant &result) +{ + bool started = CServiceBroker::GetPVRManager().IsStarted(); + + if (property == "available") + result = started; + else if (property == "recording") + { + if (started) + result = CServiceBroker::GetPVRManager().PlaybackState()->IsRecording(); + else + result = false; + } + else if (property == "scanning") + { + if (started) + result = CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().IsRunningChannelScan(); + else + result = false; + } + else + return InvalidParams; + + return OK; +} + +void CPVROperations::FillChannelGroupDetails(const std::shared_ptr<CPVRChannelGroup> &channelGroup, const CVariant ¶meterObject, CVariant &result, bool append /* = false */) +{ + if (channelGroup == NULL) + return; + + CVariant object(CVariant::VariantTypeObject); + object["channelgroupid"] = channelGroup->GroupID(); + object["channeltype"] = channelGroup->IsRadio() ? "radio" : "tv"; + object["label"] = channelGroup->GroupName(); + + if (append) + result.append(object); + else + { + CFileItemList channels; + const auto groupMembers = channelGroup->GetMembers(CPVRChannelGroup::Include::ONLY_VISIBLE); + for (const auto& groupMember : groupMembers) + { + channels.Add(std::make_shared<CFileItem>(groupMember)); + } + + object["channels"] = CVariant(CVariant::VariantTypeArray); + HandleFileItemList("channelid", false, "channels", channels, parameterObject["channels"], object, false); + + result = object; + } +} + +JSONRPC_STATUS CPVROperations::GetTimers(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRTimers> timers = CServiceBroker::GetPVRManager().Timers(); + if (!timers) + return FailedToExecute; + + CFileItemList timerList; + const std::vector<std::shared_ptr<CPVRTimerInfoTag>> tags = timers->GetAll(); + for (const auto& timer : tags) + { + timerList.Add(std::make_shared<CFileItem>(timer)); + } + + HandleFileItemList("timerid", false, "timers", timerList, parameterObject, result, true); + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetTimerDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRTimers> timers = CServiceBroker::GetPVRManager().Timers(); + if (!timers) + return FailedToExecute; + + std::shared_ptr<CPVRTimerInfoTag> timer = timers->GetById((int)parameterObject["timerid"].asInteger()); + if (!timer) + return InvalidParams; + + HandleFileItem("timerid", false, "timerdetails", CFileItemPtr(new CFileItem(timer)), parameterObject, parameterObject["properties"], result, false); + + return OK; +} + +JSONRPC_STATUS CPVROperations::AddTimer(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + const std::shared_ptr<CPVREpgInfoTag> epgTag = + CServiceBroker::GetPVRManager().EpgContainer().GetTagByDatabaseId( + parameterObject["broadcastid"].asInteger()); + + if (!epgTag) + return InvalidParams; + + if (CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epgTag)) + return InvalidParams; + + const std::shared_ptr<CPVRTimerInfoTag> newTimer = + CPVRTimerInfoTag::CreateFromEpg(epgTag, parameterObject["timerrule"].asBoolean(false), + parameterObject["reminder"].asBoolean(false)); + if (newTimer) + { + if (CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().AddTimer(newTimer)) + return ACK; + } + return FailedToExecute; +} + + +JSONRPC_STATUS CPVROperations::DeleteTimer(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRTimers> timers = CServiceBroker::GetPVRManager().Timers(); + if (!timers) + return FailedToExecute; + + std::shared_ptr<CPVRTimerInfoTag> timer = timers->GetById(parameterObject["timerid"].asInteger()); + if (!timer) + return InvalidParams; + + if (timers->DeleteTimer(timer, timer->IsRecording(), false) == TimerOperationResult::OK) + return ACK; + + return FailedToExecute; +} + +JSONRPC_STATUS CPVROperations::ToggleTimer(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + const std::shared_ptr<CPVREpgInfoTag> epgTag = + CServiceBroker::GetPVRManager().EpgContainer().GetTagByDatabaseId( + parameterObject["broadcastid"].asInteger()); + + if (!epgTag) + return InvalidParams; + + bool timerrule = parameterObject["timerrule"].asBoolean(false); + bool sentOkay = false; + std::shared_ptr<CPVRTimerInfoTag> timer = CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epgTag); + if (timer) + { + if (timerrule) + timer = CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer); + + if (timer) + sentOkay = (CServiceBroker::GetPVRManager().Timers()->DeleteTimer(timer, timer->IsRecording(), false) == TimerOperationResult::OK); + } + else + { + timer = CPVRTimerInfoTag::CreateFromEpg(epgTag, timerrule); + if (!timer) + return InvalidParams; + + sentOkay = CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().AddTimer(timer); + } + + if (sentOkay) + return ACK; + + return FailedToExecute; +} + +JSONRPC_STATUS CPVROperations::GetRecordings(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRRecordings> recordings = CServiceBroker::GetPVRManager().Recordings(); + if (!recordings) + return FailedToExecute; + + CFileItemList recordingsList; + const std::vector<std::shared_ptr<CPVRRecording>> recs = recordings->GetAll(); + for (const auto& recording : recs) + { + recordingsList.Add(std::make_shared<CFileItem>(recording)); + } + + HandleFileItemList("recordingid", true, "recordings", recordingsList, parameterObject, result, true); + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetRecordingDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRRecordings> recordings = CServiceBroker::GetPVRManager().Recordings(); + if (!recordings) + return FailedToExecute; + + const std::shared_ptr<CPVRRecording> recording = recordings->GetById(static_cast<int>(parameterObject["recordingid"].asInteger())); + if (!recording) + return InvalidParams; + + HandleFileItem("recordingid", true, "recordingdetails", std::make_shared<CFileItem>(recording), parameterObject, parameterObject["properties"], result, false); + + return OK; +} + +std::shared_ptr<CFileItem> CPVROperations::GetRecordingFileItem(int recordingId) +{ + if (CServiceBroker::GetPVRManager().IsStarted()) + { + const std::shared_ptr<PVR::CPVRRecordings> recordings = + CServiceBroker::GetPVRManager().Recordings(); + + if (recordings) + { + const std::shared_ptr<PVR::CPVRRecording> recording = recordings->GetById(recordingId); + if (recording) + return std::make_shared<CFileItem>(recording); + } + } + + return {}; +} diff --git a/xbmc/interfaces/json-rpc/PVROperations.h b/xbmc/interfaces/json-rpc/PVROperations.h new file mode 100644 index 0000000..08aa703 --- /dev/null +++ b/xbmc/interfaces/json-rpc/PVROperations.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "FileItemHandler.h" +#include "pvr/channels/PVRChannelGroup.h" + +#include <memory> + +class CVariant; + +namespace JSONRPC +{ + class CPVROperations : public CFileItemHandler + { + public: + static JSONRPC_STATUS GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetChannelGroups(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetChannelGroupDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetChannels(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetChannelDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetClients(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result); + static JSONRPC_STATUS GetBroadcasts(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetBroadcastDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetBroadcastIsPlayable(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result); + static JSONRPC_STATUS GetTimers(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetTimerDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetRecordings(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetRecordingDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS AddTimer(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS DeleteTimer(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS ToggleTimer(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS Record(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Scan(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static std::shared_ptr<CFileItem> GetRecordingFileItem(int recordingId); + + private: + static JSONRPC_STATUS GetPropertyValue(const std::string &property, CVariant &result); + static void FillChannelGroupDetails(const std::shared_ptr<PVR::CPVRChannelGroup> &channelGroup, const CVariant ¶meterObject, CVariant &result, bool append = false); + }; +} diff --git a/xbmc/interfaces/json-rpc/PlayerOperations.cpp b/xbmc/interfaces/json-rpc/PlayerOperations.cpp new file mode 100644 index 0000000..9912a72 --- /dev/null +++ b/xbmc/interfaces/json-rpc/PlayerOperations.cpp @@ -0,0 +1,2158 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "PlayerOperations.h" + +#include "AudioLibrary.h" +#include "FileItem.h" +#include "GUIInfoManager.h" +#include "GUIUserMessages.h" +#include "PartyModeManager.h" +#include "PlayListPlayer.h" +#include "SeekHandler.h" +#include "ServiceBroker.h" +#include "Util.h" +#include "VideoLibrary.h" +#include "application/Application.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "application/ApplicationPowerHandling.h" +#include "cores/playercorefactory/PlayerCoreFactory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "interfaces/builtins/Builtins.h" +#include "messaging/ApplicationMessenger.h" +#include "music/MusicDatabase.h" +#include "pictures/GUIWindowSlideShow.h" +#include "pvr/PVRManager.h" +#include "pvr/PVRPlaybackState.h" +#include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRChannelGroupMember.h" +#include "pvr/channels/PVRChannelGroupsContainer.h" +#include "pvr/epg/EpgInfoTag.h" +#include "pvr/guilib/PVRGUIActionsChannels.h" +#include "pvr/guilib/PVRGUIActionsPlayback.h" +#include "pvr/recordings/PVRRecordings.h" +#include "settings/AdvancedSettings.h" +#include "settings/DisplaySettings.h" +#include "settings/MediaSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/MathUtils.h" +#include "utils/Variant.h" +#include "video/VideoDatabase.h" + +#include <map> +#include <tuple> + +using namespace JSONRPC; +using namespace PVR; + +namespace +{ + +void AppendAudioStreamFlagsAsBooleans(CVariant& list, StreamFlags flags) +{ + list["isdefault"] = ((flags & StreamFlags::FLAG_DEFAULT) != 0); + list["isoriginal"] = ((flags & StreamFlags::FLAG_ORIGINAL) != 0); + list["isimpaired"] = ((flags & StreamFlags::FLAG_VISUAL_IMPAIRED) != 0); +} + +void AppendSubtitleStreamFlagsAsBooleans(CVariant& list, StreamFlags flags) +{ + list["isdefault"] = ((flags & StreamFlags::FLAG_DEFAULT) != 0); + list["isforced"] = ((flags & StreamFlags::FLAG_FORCED) != 0); + list["isimpaired"] = ((flags & StreamFlags::FLAG_HEARING_IMPAIRED) != 0); +} + +} // namespace + +JSONRPC_STATUS CPlayerOperations::GetActivePlayers(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int activePlayers = GetActivePlayers(); + result = CVariant(CVariant::VariantTypeArray); + + std::string strPlayerType = "internal"; + if (activePlayers & External) + strPlayerType = "external"; + else if (activePlayers & Remote) + strPlayerType = "remote"; + + if (activePlayers & Video) + { + CVariant video = CVariant(CVariant::VariantTypeObject); + video["playerid"] = GetPlaylist(Video); + video["type"] = "video"; + video["playertype"] = strPlayerType; + result.append(video); + } + if (activePlayers & Audio) + { + CVariant audio = CVariant(CVariant::VariantTypeObject); + audio["playerid"] = GetPlaylist(Audio); + audio["type"] = "audio"; + audio["playertype"] = strPlayerType; + result.append(audio); + } + if (activePlayers & Picture) + { + CVariant picture = CVariant(CVariant::VariantTypeObject); + picture["playerid"] = GetPlaylist(Picture); + picture["type"] = "picture"; + picture["playertype"] = "internal"; + result.append(picture); + } + + return OK; +} + +JSONRPC_STATUS CPlayerOperations::GetPlayers(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); + + std::string media = parameterObject["media"].asString(); + result = CVariant(CVariant::VariantTypeArray); + std::vector<std::string> players; + + if (media == "all") + { + playerCoreFactory.GetPlayers(players); + } + else + { + bool video = false; + if (media == "video") + video = true; + playerCoreFactory.GetPlayers(players, true, video); + } + + for (const auto& playername : players) + { + CVariant player(CVariant::VariantTypeObject); + player["name"] = playername; + + player["playsvideo"] = playerCoreFactory.PlaysVideo(playername); + player["playsaudio"] = playerCoreFactory.PlaysAudio(playername); + player["type"] = playerCoreFactory.GetPlayerType(playername); + + result.push_back(player); + } + + return OK; +} + +JSONRPC_STATUS CPlayerOperations::GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PlayerType player = GetPlayer(parameterObject["playerid"]); + + CVariant properties = CVariant(CVariant::VariantTypeObject); + for (unsigned int index = 0; index < parameterObject["properties"].size(); index++) + { + std::string propertyName = parameterObject["properties"][index].asString(); + CVariant property; + JSONRPC_STATUS ret; + if ((ret = GetPropertyValue(player, propertyName, property)) != OK) + return ret; + + properties[propertyName] = property; + } + + result = properties; + + return OK; +} + +JSONRPC_STATUS CPlayerOperations::GetItem(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PlayerType player = GetPlayer(parameterObject["playerid"]); + CFileItemPtr fileItem; + + switch (player) + { + case Video: + case Audio: + { + fileItem = std::make_shared<CFileItem>(g_application.CurrentFileItem()); + if (IsPVRChannel()) + break; + + if (player == Video) + { + if (!CVideoLibrary::FillFileItem(fileItem->GetPath(), fileItem, parameterObject)) + { + // Fallback to item details held by GUI but ensure path unchanged + //! @todo remove this once there is no route to playback that updates + // GUI item without also updating app item e.g. start playback of a + // non-library item via JSON + const CVideoInfoTag *currentVideoTag = CServiceBroker::GetGUI()->GetInfoManager().GetCurrentMovieTag(); + if (currentVideoTag != NULL) + { + std::string originalLabel = fileItem->GetLabel(); + std::string originalPath = fileItem->GetPath(); + fileItem->SetFromVideoInfoTag(*currentVideoTag); + if (fileItem->GetLabel().empty()) + fileItem->SetLabel(originalLabel); + fileItem->SetPath(originalPath); // Ensure path unchanged + } + } + + bool additionalInfo = false; + for (CVariant::const_iterator_array itr = parameterObject["properties"].begin_array(); + itr != parameterObject["properties"].end_array(); ++itr) + { + std::string fieldValue = itr->asString(); + if (fieldValue == "cast" || fieldValue == "set" || fieldValue == "setid" || fieldValue == "showlink" || fieldValue == "resume" || + (fieldValue == "streamdetails" && !fileItem->GetVideoInfoTag()->m_streamDetails.HasItems())) + additionalInfo = true; + } + + CVideoDatabase videodatabase; + if ((additionalInfo) && + videodatabase.Open()) + { + switch (fileItem->GetVideoContentType()) + { + case VideoDbContentType::MOVIES: + videodatabase.GetMovieInfo("", *(fileItem->GetVideoInfoTag()), + fileItem->GetVideoInfoTag()->m_iDbId); + break; + + case VideoDbContentType::MUSICVIDEOS: + videodatabase.GetMusicVideoInfo("", *(fileItem->GetVideoInfoTag()), + fileItem->GetVideoInfoTag()->m_iDbId); + break; + + case VideoDbContentType::EPISODES: + videodatabase.GetEpisodeInfo("", *(fileItem->GetVideoInfoTag()), + fileItem->GetVideoInfoTag()->m_iDbId); + break; + + case VideoDbContentType::TVSHOWS: + case VideoDbContentType::MOVIE_SETS: + default: + break; + } + + videodatabase.Close(); + } + } + else // Audio + { + if (!CAudioLibrary::FillFileItem(fileItem->GetPath(), fileItem, parameterObject)) + { + // Fallback to item details held by GUI but ensure path unchanged + //! @todo remove this once there is no route to playback that updates + // GUI item without also updating app item e.g. start playback of a + // non-library item via JSON + const MUSIC_INFO::CMusicInfoTag* currentMusicTag = + CServiceBroker::GetGUI()->GetInfoManager().GetCurrentSongTag(); + if (currentMusicTag != NULL) + { + std::string originalLabel = fileItem->GetLabel(); + std::string originalPath = fileItem->GetPath(); + fileItem->SetFromMusicInfoTag(*currentMusicTag); + if (fileItem->GetLabel().empty()) + fileItem->SetLabel(originalLabel); + fileItem->SetPath(originalPath); // Ensure path unchanged + } + } + + if (fileItem->IsMusicDb()) + { + CMusicDatabase musicdb; + CFileItemList items; + items.Add(fileItem); + CAudioLibrary::GetAdditionalSongDetails(parameterObject, items, musicdb); + } + } + break; + } + + case Picture: + { + CGUIWindowSlideShow *slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (!slideshow) + return FailedToExecute; + + CFileItemList slides; + slideshow->GetSlideShowContents(slides); + fileItem = slides[slideshow->CurrentSlide() - 1]; + break; + } + + case None: + default: + return FailedToExecute; + } + + HandleFileItem("id", !IsPVRChannel(), "item", fileItem, parameterObject, parameterObject["properties"], result, false); + return OK; +} + +JSONRPC_STATUS CPlayerOperations::PlayPause(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CGUIWindowSlideShow *slideshow = NULL; + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + case Audio: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (!appPlayer->CanPause()) + return FailedToExecute; + + if (parameterObject["play"].isString()) + CBuiltins::GetInstance().Execute("playercontrol(play)"); + else + { + if (parameterObject["play"].asBoolean()) + { + if (appPlayer->IsPausedPlayback()) + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_MEDIA_PAUSE); + else if (appPlayer->GetPlaySpeed() != 1) + appPlayer->SetPlaySpeed(1); + } + else if (!appPlayer->IsPausedPlayback()) + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_MEDIA_PAUSE); + } + result["speed"] = appPlayer->IsPausedPlayback() ? 0 : (int)lrint(appPlayer->GetPlaySpeed()); + return OK; + } + + case Picture: + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow && slideshow->IsPlaying() && + (parameterObject["play"].isString() || + (parameterObject["play"].isBoolean() && parameterObject["play"].asBoolean() == slideshow->IsPaused()))) + SendSlideshowAction(ACTION_PAUSE); + + if (slideshow && slideshow->IsPlaying() && !slideshow->IsPaused()) + result["speed"] = slideshow->GetDirection(); + else + result["speed"] = 0; + return OK; + + case None: + default: + return FailedToExecute; + } +} + +JSONRPC_STATUS CPlayerOperations::Stop(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + case Audio: + CServiceBroker::GetAppMessenger()->PostMsg( + TMSG_MEDIA_STOP, static_cast<int>(parameterObject["playerid"].asInteger())); + return ACK; + + case Picture: + SendSlideshowAction(ACTION_STOP); + return ACK; + + case None: + default: + return FailedToExecute; + } +} + +JSONRPC_STATUS CPlayerOperations::GetAudioDelay(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + result["offset"] = appPlayer->GetVideoSettings().m_AudioDelay; + return OK; +} + +JSONRPC_STATUS CPlayerOperations::SetAudioDelay(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) +{ + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + float videoAudioDelayRange = + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoAudioDelayRange; + + if (parameterObject["offset"].isDouble()) + { + float offset = static_cast<float>(parameterObject["offset"].asDouble()); + offset = MathUtils::RoundF(offset, AUDIO_DELAY_STEP); + if (offset > videoAudioDelayRange) + offset = videoAudioDelayRange; + else if (offset < -videoAudioDelayRange) + offset = -videoAudioDelayRange; + + appPlayer->SetAVDelay(offset); + } + else if (parameterObject["offset"].isString()) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + if (parameterObject["offset"].asString().compare("increment") == 0) + { + vs.m_AudioDelay += AUDIO_DELAY_STEP; + if (vs.m_AudioDelay > videoAudioDelayRange) + vs.m_AudioDelay = videoAudioDelayRange; + appPlayer->SetAVDelay(vs.m_AudioDelay); + } + else + { + vs.m_AudioDelay -= AUDIO_DELAY_STEP; + if (vs.m_AudioDelay < -videoAudioDelayRange) + vs.m_AudioDelay = -videoAudioDelayRange; + appPlayer->SetAVDelay(vs.m_AudioDelay); + } + } + else + return InvalidParams; + + result["offset"] = appPlayer->GetVideoSettings().m_AudioDelay; + return OK; + } + case Audio: + case Picture: + case None: + default: + return FailedToExecute; + } +} + +JSONRPC_STATUS CPlayerOperations::SetSpeed(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + case Audio: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (parameterObject["speed"].isInteger()) + { + int speed = (int)parameterObject["speed"].asInteger(); + if (speed != 0) + { + // If the player is paused we first need to unpause + if (appPlayer->IsPausedPlayback()) + appPlayer->Pause(); + appPlayer->SetPlaySpeed(speed); + } + else + appPlayer->Pause(); + } + else if (parameterObject["speed"].isString()) + { + if (parameterObject["speed"].asString().compare("increment") == 0) + CBuiltins::GetInstance().Execute("playercontrol(forward)"); + else + CBuiltins::GetInstance().Execute("playercontrol(rewind)"); + } + else + return InvalidParams; + + result["speed"] = appPlayer->IsPausedPlayback() ? 0 : (int)lrint(appPlayer->GetPlaySpeed()); + return OK; + } + + case Picture: + case None: + default: + return FailedToExecute; + } +} + +JSONRPC_STATUS CPlayerOperations::Seek(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PlayerType player = GetPlayer(parameterObject["playerid"]); + switch (player) + { + case Video: + case Audio: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (!appPlayer->CanSeek()) + return FailedToExecute; + + const CVariant& value = parameterObject["value"]; + if (value.isMember("percentage")) + g_application.SeekPercentage(value["percentage"].asFloat()); + else if (value.isMember("step")) + { + std::string step = value["step"].asString(); + if (step == "smallforward") + CBuiltins::GetInstance().Execute("playercontrol(smallskipforward)"); + else if (step == "smallbackward") + CBuiltins::GetInstance().Execute("playercontrol(smallskipbackward)"); + else if (step == "bigforward") + CBuiltins::GetInstance().Execute("playercontrol(bigskipforward)"); + else if (step == "bigbackward") + CBuiltins::GetInstance().Execute("playercontrol(bigskipbackward)"); + else + return InvalidParams; + } + else if (value.isMember("seconds")) + appPlayer->GetSeekHandler().SeekSeconds(static_cast<int>(value["seconds"].asInteger())); + else if (value.isMember("time")) + g_application.SeekTime(ParseTimeInSeconds(value["time"])); + else + return InvalidParams; + + GetPropertyValue(player, "percentage", result["percentage"]); + GetPropertyValue(player, "time", result["time"]); + GetPropertyValue(player, "totaltime", result["totaltime"]); + return OK; + } + + case Picture: + case None: + default: + return FailedToExecute; + } +} + +JSONRPC_STATUS CPlayerOperations::Move(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string direction = parameterObject["direction"].asString(); + switch (GetPlayer(parameterObject["playerid"])) + { + case Picture: + if (direction == "left") + SendSlideshowAction(ACTION_MOVE_LEFT); + else if (direction == "right") + SendSlideshowAction(ACTION_MOVE_RIGHT); + else if (direction == "up") + SendSlideshowAction(ACTION_MOVE_UP); + else if (direction == "down") + SendSlideshowAction(ACTION_MOVE_DOWN); + else + return InvalidParams; + + return ACK; + + case Video: + case Audio: + if (direction == "left" || direction == "up") + CServiceBroker::GetAppMessenger()->SendMsg( + TMSG_GUI_ACTION, WINDOW_INVALID, -1, static_cast<void*>(new CAction(ACTION_PREV_ITEM))); + else if (direction == "right" || direction == "down") + CServiceBroker::GetAppMessenger()->SendMsg( + TMSG_GUI_ACTION, WINDOW_INVALID, -1, static_cast<void*>(new CAction(ACTION_NEXT_ITEM))); + else + return InvalidParams; + + return ACK; + + case None: + default: + return FailedToExecute; + } +} + +JSONRPC_STATUS CPlayerOperations::Zoom(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVariant zoom = parameterObject["zoom"]; + switch (GetPlayer(parameterObject["playerid"])) + { + case Picture: + if (zoom.isInteger()) + SendSlideshowAction(ACTION_ZOOM_LEVEL_NORMAL + ((int)zoom.asInteger() - 1)); + else if (zoom.isString()) + { + std::string strZoom = zoom.asString(); + if (strZoom == "in") + SendSlideshowAction(ACTION_ZOOM_IN); + else if (strZoom == "out") + SendSlideshowAction(ACTION_ZOOM_OUT); + else + return InvalidParams; + } + else + return InvalidParams; + + return ACK; + + case Video: + case Audio: + case None: + default: + return FailedToExecute; + } +} + +// Matching pairs of values from JSON type "Player.ViewMode" and C++ enum ViewMode +// Additions to enum ViewMode need to be added here and in the JSON type +std::map<std::string, ViewMode> viewModes = +{ + {"normal", ViewModeNormal}, + {"zoom", ViewModeZoom}, + {"stretch4x3", ViewModeStretch4x3}, + {"widezoom", ViewModeWideZoom, }, + {"stretch16x9", ViewModeStretch16x9}, + {"original", ViewModeOriginal}, + {"stretch16x9nonlin", ViewModeStretch16x9Nonlin}, + {"zoom120width", ViewModeZoom120Width}, + {"zoom110width", ViewModeZoom110Width} +}; + +std::string GetStringFromViewMode(ViewMode viewMode) +{ + std::string result = "custom"; + + auto it = find_if(viewModes.begin(), viewModes.end(), [viewMode](const std::pair<std::string, ViewMode> & p) + { + return p.second == viewMode; + }); + + if (it != viewModes.end()) + { + std::pair<std::string, ViewMode> value = *it; + result = value.first; + } + + return result; +} + +void GetNewValueForViewModeParameter(const CVariant ¶meter, float stepSize, float minValue, float maxValue, float &result) +{ + if (parameter.isDouble()) + { + result = parameter.asDouble(); + } + else if (parameter.isString()) + { + if (parameter == "decrease") + { + stepSize *= -1; + } + + result += stepSize; + } + + result = std::max(minValue, std::min(result, maxValue)); +} + +JSONRPC_STATUS CPlayerOperations::SetViewMode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + JSONRPC_STATUS jsonStatus = InvalidParams; + // init with current values from settings + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + CVideoSettings vs = appPlayer->GetVideoSettings(); + ViewMode mode = ViewModeNormal; + + CVariant viewMode = parameterObject["viewmode"]; + if (viewMode.isString()) + { + std::string modestr = viewMode.asString(); + if (viewModes.find(modestr) != viewModes.end()) + { + mode = viewModes[modestr]; + jsonStatus = ACK; + } + } + else if (viewMode.isObject()) + { + mode = ViewModeCustom; + CVariant zoom = viewMode["zoom"]; + CVariant pixelRatio = viewMode["pixelratio"]; + CVariant verticalShift = viewMode["verticalshift"]; + CVariant stretch = viewMode["nonlinearstretch"]; + + if (!zoom.isNull()) + { + GetNewValueForViewModeParameter(zoom, 0.01f, 0.5f, 2.f, vs.m_CustomZoomAmount); + jsonStatus = ACK; + } + + if (!pixelRatio.isNull()) + { + GetNewValueForViewModeParameter(pixelRatio, 0.01f, 0.5f, 2.f, vs.m_CustomPixelRatio); + jsonStatus = ACK; + } + + if (!verticalShift.isNull()) + { + GetNewValueForViewModeParameter(verticalShift, -0.01f, -2.f, 2.f, vs.m_CustomVerticalShift); + jsonStatus = ACK; + } + + if (stretch.isBoolean()) + { + vs.m_CustomNonLinStretch = stretch.asBoolean(); + jsonStatus = ACK; + } + } + + if (jsonStatus == ACK) + { + appPlayer->SetRenderViewMode(static_cast<int>(mode), vs.m_CustomZoomAmount, + vs.m_CustomPixelRatio, vs.m_CustomVerticalShift, + vs.m_CustomNonLinStretch); + } + + return jsonStatus; +} + +JSONRPC_STATUS CPlayerOperations::GetViewMode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + int mode = appPlayer->GetVideoSettings().m_ViewMode; + + result["viewmode"] = GetStringFromViewMode(static_cast<ViewMode>(mode)); + + result["zoom"] = CDisplaySettings::GetInstance().GetZoomAmount(); + result["pixelratio"] = CDisplaySettings::GetInstance().GetPixelRatio(); + result["verticalshift"] = CDisplaySettings::GetInstance().GetVerticalShift(); + result["nonlinearstretch"] = CDisplaySettings::GetInstance().IsNonLinearStretched(); + return OK; +} + +JSONRPC_STATUS CPlayerOperations::Rotate(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + switch (GetPlayer(parameterObject["playerid"])) + { + case Picture: + if (parameterObject["value"].asString().compare("clockwise") == 0) + SendSlideshowAction(ACTION_ROTATE_PICTURE_CW); + else + SendSlideshowAction(ACTION_ROTATE_PICTURE_CCW); + return ACK; + + case Video: + case Audio: + case None: + default: + return FailedToExecute; + } +} + +JSONRPC_STATUS CPlayerOperations::Open(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVariant options = parameterObject["options"]; + CVariant optionShuffled = options["shuffled"]; + CVariant optionRepeat = options["repeat"]; + CVariant optionResume = options["resume"]; + CVariant optionPlayer = options["playername"]; + + if (parameterObject["item"].isMember("playlistid")) + { + PLAYLIST::Id playlistid = parameterObject["item"]["playlistid"].asInteger(); + + if (playlistid == PLAYLIST::TYPE_MUSIC || playlistid == PLAYLIST::TYPE_VIDEO) + { + // Apply the "shuffled" option if available + if (optionShuffled.isBoolean()) + CServiceBroker::GetPlaylistPlayer().SetShuffle(playlistid, optionShuffled.asBoolean(), false); + // Apply the "repeat" option if available + if (!optionRepeat.isNull()) + CServiceBroker::GetPlaylistPlayer().SetRepeat(playlistid, ParseRepeatState(optionRepeat), + false); + } + + int playlistStartPosition = (int)parameterObject["item"]["position"].asInteger(); + + switch (playlistid) + { + case PLAYLIST::TYPE_MUSIC: + case PLAYLIST::TYPE_VIDEO: + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, playlistid, + playlistStartPosition); + break; + + case PLAYLIST::TYPE_PICTURE: + { + std::string firstPicturePath; + if (playlistStartPosition > 0) + { + CGUIWindowSlideShow *slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow != NULL) + { + CFileItemList list; + slideshow->GetSlideShowContents(list); + if (playlistStartPosition < list.Size()) + firstPicturePath = list.Get(playlistStartPosition)->GetPath(); + } + } + + return StartSlideshow("", false, optionShuffled.isBoolean() && optionShuffled.asBoolean(), firstPicturePath); + break; + } + } + + return ACK; + } + else if (parameterObject["item"].isMember("path")) + { + bool random = (optionShuffled.isBoolean() && optionShuffled.asBoolean()) || + (!optionShuffled.isBoolean() && parameterObject["item"]["random"].asBoolean()); + return StartSlideshow(parameterObject["item"]["path"].asString(), parameterObject["item"]["recursive"].asBoolean(), random); + } + else if (parameterObject["item"].isObject() && parameterObject["item"].isMember("partymode")) + { + if (g_partyModeManager.IsEnabled()) + g_partyModeManager.Disable(); + CServiceBroker::GetAppMessenger()->PostMsg( + TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, + "playercontrol(partymode(" + parameterObject["item"]["partymode"].asString() + "))"); + return ACK; + } + else if (parameterObject["item"].isMember("broadcastid")) + { + const std::shared_ptr<CPVREpgInfoTag> epgTag = + CServiceBroker::GetPVRManager().EpgContainer().GetTagByDatabaseId( + static_cast<unsigned int>(parameterObject["item"]["broadcastid"].asInteger())); + + if (!epgTag || !epgTag->IsPlayable()) + return InvalidParams; + + if (!CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().PlayEpgTag(CFileItem(epgTag))) + return FailedToExecute; + + return ACK; + } + else if (parameterObject["item"].isMember("channelid")) + { + const std::shared_ptr<CPVRChannelGroupsContainer> channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + if (!channelGroupContainer) + return FailedToExecute; + + const std::shared_ptr<CPVRChannel> channel = channelGroupContainer->GetChannelById(static_cast<int>(parameterObject["item"]["channelid"].asInteger())); + if (!channel) + return InvalidParams; + + const std::shared_ptr<CPVRChannelGroupMember> groupMember = + CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetChannelGroupMember(channel); + if (!groupMember) + return InvalidParams; + + if (!CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().PlayMedia( + CFileItem(groupMember))) + return FailedToExecute; + + return ACK; + } + else if (parameterObject["item"].isMember("recordingid")) + { + const std::shared_ptr<CPVRRecordings> recordingsContainer = CServiceBroker::GetPVRManager().Recordings(); + if (!recordingsContainer) + return FailedToExecute; + + const std::shared_ptr<CPVRRecording> recording = recordingsContainer->GetById(static_cast<int>(parameterObject["item"]["recordingid"].asInteger())); + if (!recording) + return InvalidParams; + + if (!CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().PlayMedia(CFileItem(recording))) + return FailedToExecute; + + return ACK; + } + else + { + CFileItemList list; + if (FillFileItemList(parameterObject["item"], list) && list.Size() > 0) + { + bool slideshow = true; + for (int index = 0; index < list.Size(); index++) + { + if (!list[index]->IsPicture()) + { + slideshow = false; + break; + } + } + + if (slideshow) + { + CGUIWindowSlideShow *slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (!slideshow) + return FailedToExecute; + + SendSlideshowAction(ACTION_STOP); + slideshow->Reset(); + for (int index = 0; index < list.Size(); index++) + slideshow->Add(list[index].get()); + + return StartSlideshow("", false, optionShuffled.isBoolean() && optionShuffled.asBoolean()); + } + else + { + std::string playername; + // Handle the "playerid" option + if (!optionPlayer.isNull()) + { + if (optionPlayer.isString()) + { + playername = optionPlayer.asString(); + + if (playername != "default") + { + const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); + + // check if the there's actually a player with the given name + if (playerCoreFactory.GetPlayerType(playername).empty()) + return InvalidParams; + + // check if the player can handle at least the first item in the list + std::vector<std::string> possiblePlayers; + playerCoreFactory.GetPlayers(*list.Get(0).get(), possiblePlayers); + + bool match = false; + for (const auto& entry : possiblePlayers) + { + if (StringUtils::EqualsNoCase(entry, playername)) + { + match = true; + break; + } + } + if (!match) + return InvalidParams; + } + } + else + return InvalidParams; + } + + // Handle "shuffled" option + if (optionShuffled.isBoolean()) + list.SetProperty("shuffled", optionShuffled); + // Handle "repeat" option + if (!optionRepeat.isNull()) + list.SetProperty("repeat", static_cast<int>(ParseRepeatState(optionRepeat))); + // Handle "resume" option + if (list.Size() == 1) + { + if (optionResume.isBoolean() && optionResume.asBoolean()) + list[0]->SetStartOffset(STARTOFFSET_RESUME); + else if (optionResume.isDouble()) + list[0]->SetProperty("StartPercent", optionResume); + else if (optionResume.isObject()) + list[0]->SetStartOffset( + CUtil::ConvertSecsToMilliSecs(ParseTimeInSeconds(optionResume))); + } + + auto l = new CFileItemList(); //don't delete + l->Copy(list); + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, -1, -1, static_cast<void*>(l), + playername); + } + + return ACK; + } + else + return InvalidParams; + } + + return InvalidParams; +} + +JSONRPC_STATUS CPlayerOperations::GoTo(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVariant to = parameterObject["to"]; + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + case Audio: + if (to.isString()) + { + std::string strTo = to.asString(); + int actionID; + if (strTo == "previous") + actionID = ACTION_PREV_ITEM; + else if (strTo == "next") + actionID = ACTION_NEXT_ITEM; + else + return InvalidParams; + + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast<void*>(new CAction(actionID))); + } + else if (to.isInteger()) + { + if (IsPVRChannel()) + CServiceBroker::GetAppMessenger()->SendMsg( + TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast<void*>( + new CAction(ACTION_CHANNEL_SWITCH, static_cast<float>(to.asInteger())))); + else + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_PLAYLISTPLAYER_PLAY, + static_cast<int>(to.asInteger())); + } + else + return InvalidParams; + break; + + case Picture: + if (to.isString()) + { + std::string strTo = to.asString(); + int actionID; + if (strTo == "previous") + actionID = ACTION_PREV_PICTURE; + else if (strTo == "next") + actionID = ACTION_NEXT_PICTURE; + else + return InvalidParams; + + SendSlideshowAction(actionID); + } + else + return FailedToExecute; + break; + + case None: + default: + return FailedToExecute; + } + + return ACK; +} + +JSONRPC_STATUS CPlayerOperations::SetShuffle(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CGUIWindowSlideShow *slideshow = NULL; + CVariant shuffle = parameterObject["shuffle"]; + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + case Audio: + { + if (IsPVRChannel()) + return FailedToExecute; + + PLAYLIST::Id playlistid = GetPlaylist(GetPlayer(parameterObject["playerid"])); + if (CServiceBroker::GetPlaylistPlayer().IsShuffled(playlistid)) + { + if ((shuffle.isBoolean() && !shuffle.asBoolean()) || + (shuffle.isString() && shuffle.asString() == "toggle")) + { + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_PLAYLISTPLAYER_SHUFFLE, playlistid, 0); + } + } + else + { + if ((shuffle.isBoolean() && shuffle.asBoolean()) || + (shuffle.isString() && shuffle.asString() == "toggle")) + { + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_PLAYLISTPLAYER_SHUFFLE, playlistid, 1); + } + } + break; + } + + case Picture: + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow == NULL) + return FailedToExecute; + if (slideshow->IsShuffled()) + { + if ((shuffle.isBoolean() && !shuffle.asBoolean()) || + (shuffle.isString() && shuffle.asString() == "toggle")) + return FailedToExecute; + } + else + { + if ((shuffle.isBoolean() && shuffle.asBoolean()) || + (shuffle.isString() && shuffle.asString() == "toggle")) + slideshow->Shuffle(); + } + break; + + default: + return FailedToExecute; + } + return ACK; +} + +JSONRPC_STATUS CPlayerOperations::SetRepeat(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + case Audio: + { + if (IsPVRChannel()) + return FailedToExecute; + + PLAYLIST::RepeatState repeat = PLAYLIST::RepeatState::NONE; + PLAYLIST::Id playlistid = GetPlaylist(GetPlayer(parameterObject["playerid"])); + if (parameterObject["repeat"].asString() == "cycle") + { + PLAYLIST::RepeatState repeatPrev = + CServiceBroker::GetPlaylistPlayer().GetRepeat(playlistid); + if (repeatPrev == PLAYLIST::RepeatState::NONE) + repeat = PLAYLIST::RepeatState::ALL; + else if (repeatPrev == PLAYLIST::RepeatState::ALL) + repeat = PLAYLIST::RepeatState::ONE; + else + repeat = PLAYLIST::RepeatState::NONE; + } + else + repeat = ParseRepeatState(parameterObject["repeat"]); + + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_PLAYLISTPLAYER_REPEAT, playlistid, + static_cast<int>(repeat)); + break; + } + + case Picture: + default: + return FailedToExecute; + } + + return ACK; +} + +JSONRPC_STATUS CPlayerOperations::SetPartymode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PlayerType player = GetPlayer(parameterObject["playerid"]); + switch (player) + { + case Video: + case Audio: + { + if (IsPVRChannel()) + return FailedToExecute; + + bool change = false; + PartyModeContext context = PARTYMODECONTEXT_UNKNOWN; + std::string strContext; + if (player == Video) + { + context = PARTYMODECONTEXT_VIDEO; + strContext = "video"; + } + else if (player == Audio) + { + context = PARTYMODECONTEXT_MUSIC; + strContext = "music"; + } + + bool toggle = parameterObject["partymode"].isString(); + if (g_partyModeManager.IsEnabled()) + { + if (g_partyModeManager.GetType() != context) + return InvalidParams; + + if (toggle || parameterObject["partymode"].asBoolean() == false) + change = true; + } + else + { + if (toggle || parameterObject["partymode"].asBoolean() == true) + change = true; + } + + if (change) + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, + "playercontrol(partymode(" + strContext + "))"); + break; + } + + case Picture: + default: + return FailedToExecute; + } + + return ACK; +} + +JSONRPC_STATUS CPlayerOperations::SetAudioStream(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->HasPlayer()) + { + int index = -1; + if (parameterObject["stream"].isString()) + { + std::string action = parameterObject["stream"].asString(); + if (action.compare("previous") == 0) + { + index = appPlayer->GetAudioStream() - 1; + if (index < 0) + index = appPlayer->GetAudioStreamCount() - 1; + } + else if (action.compare("next") == 0) + { + index = appPlayer->GetAudioStream() + 1; + if (index >= appPlayer->GetAudioStreamCount()) + index = 0; + } + else + return InvalidParams; + } + else if (parameterObject["stream"].isInteger()) + index = (int)parameterObject["stream"].asInteger(); + + if (index < 0 || appPlayer->GetAudioStreamCount() <= index) + return InvalidParams; + + appPlayer->SetAudioStream(index); + } + else + return FailedToExecute; + break; + } + + case Audio: + case Picture: + default: + return FailedToExecute; + } + + return ACK; +} + +JSONRPC_STATUS CPlayerOperations::AddSubtitle(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (GetPlayer(parameterObject["playerid"]) != Video) + return FailedToExecute; + + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + if (!appPlayer->HasPlayer()) + return FailedToExecute; + + if (!parameterObject["subtitle"].isString()) + return FailedToExecute; + + std::string sub = parameterObject["subtitle"].asString(); + appPlayer->AddSubtitle(sub); + return ACK; +} + +JSONRPC_STATUS CPlayerOperations::SetSubtitle(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->HasPlayer()) + { + int index = -1; + if (parameterObject["subtitle"].isString()) + { + std::string action = parameterObject["subtitle"].asString(); + if (action.compare("previous") == 0) + { + index = appPlayer->GetSubtitle() - 1; + if (index < 0) + index = appPlayer->GetSubtitleCount() - 1; + } + else if (action.compare("next") == 0) + { + index = appPlayer->GetSubtitle() + 1; + if (index >= appPlayer->GetSubtitleCount()) + index = 0; + } + else if (action.compare("off") == 0) + { + appPlayer->SetSubtitleVisible(false); + return ACK; + } + else if (action.compare("on") == 0) + { + appPlayer->SetSubtitleVisible(true); + return ACK; + } + else + return InvalidParams; + } + else if (parameterObject["subtitle"].isInteger()) + index = (int)parameterObject["subtitle"].asInteger(); + + if (index < 0 || appPlayer->GetSubtitleCount() <= index) + return InvalidParams; + + appPlayer->SetSubtitle(index); + + // Check if we need to enable subtitles to be displayed + if (parameterObject["enable"].asBoolean() && !appPlayer->GetSubtitleVisible()) + appPlayer->SetSubtitleVisible(true); + } + else + return FailedToExecute; + break; + } + + case Audio: + case Picture: + default: + return FailedToExecute; + } + + return ACK; +} + +JSONRPC_STATUS CPlayerOperations::SetVideoStream(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + int streamCount = appPlayer->GetVideoStreamCount(); + if (streamCount > 0) + { + int index = appPlayer->GetVideoStream(); + if (parameterObject["stream"].isString()) + { + std::string action = parameterObject["stream"].asString(); + if (action.compare("previous") == 0) + { + index--; + if (index < 0) + index = streamCount - 1; + } + else if (action.compare("next") == 0) + { + index++; + if (index >= streamCount) + index = 0; + } + else + return InvalidParams; + } + else if (parameterObject["stream"].isInteger()) + index = (int)parameterObject["stream"].asInteger(); + + if (index < 0 || streamCount <= index) + return InvalidParams; + + appPlayer->SetVideoStream(index); + } + else + return FailedToExecute; + break; + } + case Audio: + case Picture: + default: + return FailedToExecute; + } + + return ACK; +} + +int CPlayerOperations::GetActivePlayers() +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + int activePlayers = 0; + if (appPlayer->IsPlayingVideo() || + CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingTV() || + CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRecording()) + activePlayers |= Video; + if (appPlayer->IsPlayingAudio() || + CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRadio()) + activePlayers |= Audio; + if (CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_SLIDESHOW)) + activePlayers |= Picture; + if (appPlayer->IsExternalPlaying()) + activePlayers |= External; + if (appPlayer->IsRemotePlaying()) + activePlayers |= Remote; + + return activePlayers; +} + +PlayerType CPlayerOperations::GetPlayer(const CVariant &player) +{ + PLAYLIST::Id playerPlaylistId = player.asInteger(); + PlayerType playerID; + + switch (playerPlaylistId) + { + case PLAYLIST::TYPE_VIDEO: + playerID = Video; + break; + + case PLAYLIST::TYPE_MUSIC: + playerID = Audio; + break; + + case PLAYLIST::TYPE_PICTURE: + playerID = Picture; + break; + + default: + playerID = None; + break; + } + + if (GetPlaylist(playerID) == playerPlaylistId) + return playerID; + else + return None; +} + +PLAYLIST::Id CPlayerOperations::GetPlaylist(PlayerType player) +{ + PLAYLIST::Id playlistId = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist(); + if (playlistId == PLAYLIST::TYPE_NONE) // No active playlist, try guessing + { + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + playlistId = appPlayer->GetPreferredPlaylist(); + } + + switch (player) + { + case Video: + return playlistId == PLAYLIST::TYPE_NONE ? PLAYLIST::TYPE_VIDEO : playlistId; + + case Audio: + return playlistId == PLAYLIST::TYPE_NONE ? PLAYLIST::TYPE_MUSIC : playlistId; + + case Picture: + return PLAYLIST::TYPE_PICTURE; + + default: + return playlistId; + } +} + +JSONRPC_STATUS CPlayerOperations::StartSlideshow(const std::string& path, bool recursive, bool random, const std::string &firstPicturePath /* = "" */) +{ + int flags = 0; + if (recursive) + flags |= 1; + if (random) + flags |= 2; + else + flags |= 4; + + std::vector<std::string> params; + params.push_back(path); + if (!firstPicturePath.empty()) + params.push_back(firstPicturePath); + + // Reset screensaver when started from JSON only to avoid potential conflict with slideshow screensavers + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->ResetScreenSaver(); + appPower->WakeUpScreenSaverAndDPMS(); + CGUIMessage msg(GUI_MSG_START_SLIDESHOW, 0, 0, flags); + msg.SetStringParams(params); + CServiceBroker::GetAppMessenger()->SendGUIMessage(msg, WINDOW_SLIDESHOW); + + return ACK; +} + +void CPlayerOperations::SendSlideshowAction(int actionID) +{ + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_SLIDESHOW, -1, + static_cast<void*>(new CAction(actionID))); +} + +JSONRPC_STATUS CPlayerOperations::GetPropertyValue(PlayerType player, const std::string &property, CVariant &result) +{ + if (player == None) + return FailedToExecute; + + PLAYLIST::Id playlistId = GetPlaylist(player); + + if (property == "type") + { + switch (player) + { + case Video: + result = "video"; + break; + + case Audio: + result = "audio"; + break; + + case Picture: + result = "picture"; + break; + + default: + return FailedToExecute; + } + } + else if (property == "partymode") + { + switch (player) + { + case Video: + case Audio: + if (IsPVRChannel()) + { + result = false; + break; + } + + result = g_partyModeManager.IsEnabled(); + break; + + case Picture: + result = false; + break; + + default: + return FailedToExecute; + } + } + else if (property == "speed") + { + CGUIWindowSlideShow *slideshow = NULL; + switch (player) + { + case Video: + case Audio: + { + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + result = appPlayer->IsPausedPlayback() ? 0 : (int)lrint(appPlayer->GetPlaySpeed()); + break; + } + + case Picture: + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow && slideshow->IsPlaying() && !slideshow->IsPaused()) + result = slideshow->GetDirection(); + else + result = 0; + break; + + default: + return FailedToExecute; + } + } + else if (property == "time") + { + switch (player) + { + case Video: + case Audio: + { + int ms = 0; + if (!IsPVRChannel()) + ms = (int)(g_application.GetTime() * 1000.0); + else + { + std::shared_ptr<CPVREpgInfoTag> epg(GetCurrentEpg()); + if (epg) + ms = epg->Progress() * 1000; + } + + MillisecondsToTimeObject(ms, result); + break; + } + + case Picture: + MillisecondsToTimeObject(0, result); + break; + + default: + return FailedToExecute; + } + } + else if (property == "percentage") + { + CGUIWindowSlideShow *slideshow = NULL; + switch (player) + { + case Video: + case Audio: + { + if (!IsPVRChannel()) + result = g_application.GetPercentage(); + else + { + std::shared_ptr<CPVREpgInfoTag> epg(GetCurrentEpg()); + if (epg) + result = epg->ProgressPercentage(); + else + result = 0; + } + break; + } + + case Picture: + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow && slideshow->NumSlides() > 0) + result = (double)slideshow->CurrentSlide() / slideshow->NumSlides(); + else + result = 0.0; + break; + + default: + return FailedToExecute; + } + } + else if (property == "cachepercentage") + { + switch (player) + { + case Video: + case Audio: + { + result = g_application.GetCachePercentage(); + break; + } + + case Picture: + { + result = 0.0; + break; + } + + default: + return FailedToExecute; + } + } + else if (property == "totaltime") + { + switch (player) + { + case Video: + case Audio: + { + int ms = 0; + if (!IsPVRChannel()) + ms = (int)(g_application.GetTotalTime() * 1000.0); + else + { + std::shared_ptr<CPVREpgInfoTag> epg(GetCurrentEpg()); + if (epg) + ms = epg->GetDuration() * 1000; + } + + MillisecondsToTimeObject(ms, result); + break; + } + + case Picture: + MillisecondsToTimeObject(0, result); + break; + + default: + return FailedToExecute; + } + } + else if (property == "playlistid") + { + result = playlistId; + } + else if (property == "position") + { + CGUIWindowSlideShow *slideshow = NULL; + switch (player) + { + case Video: + case Audio: /* Return the position of current item if there is an active playlist */ + if (!IsPVRChannel() && + CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == playlistId) + { + result = CServiceBroker::GetPlaylistPlayer().GetCurrentSong(); + } + else + result = -1; + break; + + case Picture: + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow && slideshow->IsPlaying()) + result = slideshow->CurrentSlide() - 1; + else + result = -1; + break; + + default: + result = -1; + break; + } + } + else if (property == "repeat") + { + switch (player) + { + case Video: + case Audio: + if (IsPVRChannel()) + { + result = "off"; + break; + } + + switch (CServiceBroker::GetPlaylistPlayer().GetRepeat(playlistId)) + { + case PLAYLIST::RepeatState::ONE: + result = "one"; + break; + case PLAYLIST::RepeatState::ALL: + result = "all"; + break; + default: + result = "off"; + break; + } + break; + + case Picture: + default: + result = "off"; + break; + } + } + else if (property == "shuffled") + { + CGUIWindowSlideShow *slideshow = NULL; + switch (player) + { + case Video: + case Audio: + if (IsPVRChannel()) + { + result = false; + break; + } + + result = CServiceBroker::GetPlaylistPlayer().IsShuffled(playlistId); + break; + + case Picture: + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow && slideshow->IsPlaying()) + result = slideshow->IsShuffled(); + else + result = -1; + break; + + default: + result = -1; + break; + } + } + else if (property == "canseek") + { + switch (player) + { + case Video: + case Audio: + { + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + result = appPlayer->CanSeek(); + break; + } + + case Picture: + default: + result = false; + break; + } + } + else if (property == "canchangespeed") + { + switch (player) + { + case Video: + case Audio: + result = !IsPVRChannel(); + break; + + case Picture: + default: + result = false; + break; + } + } + else if (property == "canmove") + { + switch (player) + { + case Picture: + result = true; + break; + + case Video: + case Audio: + default: + result = false; + break; + } + } + else if (property == "canzoom") + { + switch (player) + { + case Picture: + result = true; + break; + + case Video: + case Audio: + default: + result = false; + break; + } + } + else if (property == "canrotate") + { + switch (player) + { + case Picture: + result = true; + break; + + case Video: + case Audio: + default: + result = false; + break; + } + } + else if (property == "canshuffle") + { + switch (player) + { + case Video: + case Audio: + case Picture: + result = !IsPVRChannel(); + break; + + default: + result = false; + break; + } + } + else if (property == "canrepeat") + { + switch (player) + { + case Video: + case Audio: + result = !IsPVRChannel(); + break; + + case Picture: + default: + result = false; + break; + } + } + else if (property == "currentaudiostream") + { + switch (player) + { + case Video: + case Audio: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->HasPlayer()) + { + result = CVariant(CVariant::VariantTypeObject); + int index = appPlayer->GetAudioStream(); + if (index >= 0) + { + AudioStreamInfo info; + appPlayer->GetAudioStreamInfo(index, info); + + result["index"] = index; + result["name"] = info.name; + result["language"] = info.language; + result["codec"] = info.codecName; + result["bitrate"] = info.bitrate; + result["channels"] = info.channels; + result["samplerate"] = info.samplerate; + AppendAudioStreamFlagsAsBooleans(result, info.flags); + } + } + else + result = CVariant(CVariant::VariantTypeNull); + break; + } + + case Picture: + default: + result = CVariant(CVariant::VariantTypeNull); + break; + } + } + else if (property == "audiostreams") + { + result = CVariant(CVariant::VariantTypeArray); + switch (player) + { + case Video: + case Audio: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->HasPlayer()) + { + for (int index = 0; index < appPlayer->GetAudioStreamCount(); index++) + { + AudioStreamInfo info; + appPlayer->GetAudioStreamInfo(index, info); + + CVariant audioStream(CVariant::VariantTypeObject); + audioStream["index"] = index; + audioStream["name"] = info.name; + audioStream["language"] = info.language; + audioStream["codec"] = info.codecName; + audioStream["bitrate"] = info.bitrate; + audioStream["channels"] = info.channels; + audioStream["samplerate"] = info.samplerate; + AppendAudioStreamFlagsAsBooleans(audioStream, info.flags); + + result.append(audioStream); + } + } + break; + } + + case Picture: + default: + break; + } + } + else if (property == "currentvideostream") + { + switch (player) + { + case Video: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + int index = appPlayer->GetVideoStream(); + if (index >= 0) + { + result = CVariant(CVariant::VariantTypeObject); + VideoStreamInfo info; + appPlayer->GetVideoStreamInfo(index, info); + + result["index"] = index; + result["name"] = info.name; + result["language"] = info.language; + result["codec"] = info.codecName; + result["width"] = info.width; + result["height"] = info.height; + } + else + result = CVariant(CVariant::VariantTypeNull); + break; + } + case Audio: + case Picture: + default: + result = CVariant(CVariant::VariantTypeNull); + break; + } + } + else if (property == "videostreams") + { + result = CVariant(CVariant::VariantTypeArray); + switch (player) + { + case Video: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + int streamCount = appPlayer->GetVideoStreamCount(); + if (streamCount >= 0) + { + for (int index = 0; index < streamCount; ++index) + { + VideoStreamInfo info; + appPlayer->GetVideoStreamInfo(index, info); + + CVariant videoStream(CVariant::VariantTypeObject); + videoStream["index"] = index; + videoStream["name"] = info.name; + videoStream["language"] = info.language; + videoStream["codec"] = info.codecName; + videoStream["width"] = info.width; + videoStream["height"] = info.height; + + result.append(videoStream); + } + } + break; + } + case Audio: + case Picture: + default: + break; + } + } + else if (property == "subtitleenabled") + { + switch (player) + { + case Video: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + result = appPlayer->GetSubtitleVisible(); + break; + } + + case Audio: + case Picture: + default: + result = false; + break; + } + } + else if (property == "currentsubtitle") + { + switch (player) + { + case Video: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->HasPlayer()) + { + result = CVariant(CVariant::VariantTypeObject); + int index = appPlayer->GetSubtitle(); + if (index >= 0) + { + SubtitleStreamInfo info; + appPlayer->GetSubtitleStreamInfo(index, info); + + result["index"] = index; + result["name"] = info.name; + result["language"] = info.language; + AppendSubtitleStreamFlagsAsBooleans(result, info.flags); + } + } + else + result = CVariant(CVariant::VariantTypeNull); + break; + } + + case Audio: + case Picture: + default: + result = CVariant(CVariant::VariantTypeNull); + break; + } + } + else if (property == "subtitles") + { + result = CVariant(CVariant::VariantTypeArray); + switch (player) + { + case Video: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->HasPlayer()) + { + for (int index = 0; index < appPlayer->GetSubtitleCount(); index++) + { + SubtitleStreamInfo info; + appPlayer->GetSubtitleStreamInfo(index, info); + + CVariant subtitle(CVariant::VariantTypeObject); + subtitle["index"] = index; + subtitle["name"] = info.name; + subtitle["language"] = info.language; + AppendSubtitleStreamFlagsAsBooleans(subtitle, info.flags); + + result.append(subtitle); + } + } + break; + } + + case Audio: + case Picture: + default: + break; + } + } + else if (property == "live") + result = IsPVRChannel(); + else + return InvalidParams; + + return OK; +} + +PLAYLIST::RepeatState CPlayerOperations::ParseRepeatState(const CVariant& repeat) +{ + PLAYLIST::RepeatState state = PLAYLIST::RepeatState::NONE; + std::string strState = repeat.asString(); + + if (strState.compare("one") == 0) + state = PLAYLIST::RepeatState::ONE; + else if (strState.compare("all") == 0) + state = PLAYLIST::RepeatState::ALL; + + return state; +} + +double CPlayerOperations::ParseTimeInSeconds(const CVariant &time) +{ + double seconds = 0.0; + if (time.isMember("hours")) + seconds += time["hours"].asInteger() * 60 * 60; + if (time.isMember("minutes")) + seconds += time["minutes"].asInteger() * 60; + if (time.isMember("seconds")) + seconds += time["seconds"].asInteger(); + if (time.isMember("milliseconds")) + seconds += time["milliseconds"].asDouble() / 1000.0; + + return seconds; +} + +bool CPlayerOperations::IsPVRChannel() +{ + const std::shared_ptr<CPVRPlaybackState> state = CServiceBroker::GetPVRManager().PlaybackState(); + return state->IsPlayingTV() || state->IsPlayingRadio(); +} + +std::shared_ptr<CPVREpgInfoTag> CPlayerOperations::GetCurrentEpg() +{ + const std::shared_ptr<CPVRPlaybackState> state = CServiceBroker::GetPVRManager().PlaybackState(); + if (!state->IsPlayingTV() && !state->IsPlayingRadio()) + return {}; + + const std::shared_ptr<CPVRChannel> currentChannel = state->GetPlayingChannel(); + if (!currentChannel) + return {}; + + return currentChannel->GetEPGNow(); +} diff --git a/xbmc/interfaces/json-rpc/PlayerOperations.h b/xbmc/interfaces/json-rpc/PlayerOperations.h new file mode 100644 index 0000000..b681bec --- /dev/null +++ b/xbmc/interfaces/json-rpc/PlayerOperations.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "FileItemHandler.h" +#include "JSONRPC.h" + +#include <string> + +class CVariant; + +namespace PVR +{ +class CPVRChannelGroup; +class CPVREpgInfoTag; +} + +namespace PLAYLIST +{ +using Id = int; +enum class RepeatState; +} // namespace PLAYLIST + +namespace JSONRPC +{ + enum PlayerType + { + None = 0, + Video = 0x1, + Audio = 0x2, + Picture = 0x4, + External = 0x8, + Remote = 0x10 + }; + + static const int PlayerImplicit = (Video | Audio | Picture); + + class CPlayerOperations : CFileItemHandler + { + public: + static JSONRPC_STATUS GetActivePlayers(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetPlayers(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetItem(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS PlayPause(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Stop(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetAudioDelay(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result); + static JSONRPC_STATUS SetAudioDelay(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result); + static JSONRPC_STATUS SetSpeed(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Seek(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS Move(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Zoom(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetViewMode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetViewMode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Rotate(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS Open(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GoTo(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetShuffle(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetRepeat(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetPartymode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS SetAudioStream(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS AddSubtitle(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetSubtitle(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetVideoStream(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + private: + static int GetActivePlayers(); + static PlayerType GetPlayer(const CVariant &player); + static PLAYLIST::Id GetPlaylist(PlayerType player); + static JSONRPC_STATUS StartSlideshow(const std::string& path, bool recursive, bool random, const std::string &firstPicturePath = ""); + static void SendSlideshowAction(int actionID); + static JSONRPC_STATUS GetPropertyValue(PlayerType player, const std::string &property, CVariant &result); + + static PLAYLIST::RepeatState ParseRepeatState(const CVariant& repeat); + static double ParseTimeInSeconds(const CVariant &time); + static bool IsPVRChannel(); + static std::shared_ptr<PVR::CPVREpgInfoTag> GetCurrentEpg(); + }; +} diff --git a/xbmc/interfaces/json-rpc/PlaylistOperations.cpp b/xbmc/interfaces/json-rpc/PlaylistOperations.cpp new file mode 100644 index 0000000..b98540a --- /dev/null +++ b/xbmc/interfaces/json-rpc/PlaylistOperations.cpp @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "PlaylistOperations.h" + +#include "FileItem.h" +#include "GUIUserMessages.h" +#include "PlayListPlayer.h" +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "input/Key.h" +#include "messaging/ApplicationMessenger.h" +#include "pictures/GUIWindowSlideShow.h" +#include "pictures/PictureInfoTag.h" +#include "utils/Variant.h" + +using namespace JSONRPC; + +JSONRPC_STATUS CPlaylistOperations::GetPlaylists(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + result = CVariant(CVariant::VariantTypeArray); + CVariant playlist = CVariant(CVariant::VariantTypeObject); + + playlist["playlistid"] = PLAYLIST::TYPE_MUSIC; + playlist["type"] = "audio"; + result.append(playlist); + + playlist["playlistid"] = PLAYLIST::TYPE_VIDEO; + playlist["type"] = "video"; + result.append(playlist); + + playlist["playlistid"] = PLAYLIST::TYPE_PICTURE; + playlist["type"] = "picture"; + result.append(playlist); + + return OK; +} + +JSONRPC_STATUS CPlaylistOperations::GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PLAYLIST::Id playlistId = GetPlaylist(parameterObject["playlistid"]); + for (unsigned int index = 0; index < parameterObject["properties"].size(); index++) + { + std::string propertyName = parameterObject["properties"][index].asString(); + CVariant property; + JSONRPC_STATUS ret; + if ((ret = GetPropertyValue(playlistId, propertyName, property)) != OK) + return ret; + + result[propertyName] = property; + } + + return OK; +} + +JSONRPC_STATUS CPlaylistOperations::GetItems(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CFileItemList list; + PLAYLIST::Id playlistId = GetPlaylist(parameterObject["playlistid"]); + + CGUIWindowSlideShow *slideshow = NULL; + switch (playlistId) + { + case PLAYLIST::TYPE_VIDEO: + case PLAYLIST::TYPE_MUSIC: + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_PLAYLISTPLAYER_GET_ITEMS, playlistId, -1, + static_cast<void*>(&list)); + break; + + case PLAYLIST::TYPE_PICTURE: + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow) + slideshow->GetSlideShowContents(list); + break; + } + + HandleFileItemList("id", true, "items", list, parameterObject, result); + + return OK; +} + +bool CPlaylistOperations::CheckMediaParameter(PLAYLIST::Id playlistId, const CVariant& itemObject) +{ + if (itemObject.isMember("media") && itemObject["media"].asString().compare("files") != 0) + { + if (playlistId == PLAYLIST::TYPE_VIDEO && itemObject["media"].asString().compare("video") != 0) + return false; + if (playlistId == PLAYLIST::TYPE_MUSIC && itemObject["media"].asString().compare("music") != 0) + return false; + if (playlistId == PLAYLIST::TYPE_PICTURE && + itemObject["media"].asString().compare("video") != 0 && + itemObject["media"].asString().compare("pictures") != 0) + return false; + } + return true; +} + +JSONRPC_STATUS CPlaylistOperations::Add(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PLAYLIST::Id playlistId = GetPlaylist(parameterObject["playlistid"]); + + CGUIWindowSlideShow *slideshow = NULL; + if (playlistId == PLAYLIST::TYPE_PICTURE) + { + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow == NULL) + return FailedToExecute; + } + + CFileItemList list; + if (!HandleItemsParameter(playlistId, parameterObject["item"], list)) + return InvalidParams; + + switch (playlistId) + { + case PLAYLIST::TYPE_VIDEO: + case PLAYLIST::TYPE_MUSIC: + { + auto tmpList = new CFileItemList(); + tmpList->Copy(list); + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_PLAYLISTPLAYER_ADD, playlistId, -1, + static_cast<void*>(tmpList)); + break; + } + case PLAYLIST::TYPE_PICTURE: + for (int index = 0; index < list.Size(); index++) + { + CPictureInfoTag picture = CPictureInfoTag(); + if (!picture.Load(list[index]->GetPath())) + continue; + + *list[index]->GetPictureInfoTag() = picture; + slideshow->Add(list[index].get()); + } + break; + + default: + return InvalidParams; + } + + return ACK; +} + +JSONRPC_STATUS CPlaylistOperations::Insert(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PLAYLIST::Id playlistId = GetPlaylist(parameterObject["playlistid"]); + if (playlistId == PLAYLIST::TYPE_PICTURE) + return FailedToExecute; + + CFileItemList list; + if (!HandleItemsParameter(playlistId, parameterObject["item"], list)) + return InvalidParams; + + auto tmpList = new CFileItemList(); + tmpList->Copy(list); + CServiceBroker::GetAppMessenger()->PostMsg( + TMSG_PLAYLISTPLAYER_INSERT, playlistId, + static_cast<int>(parameterObject["position"].asInteger()), static_cast<void*>(tmpList)); + + return ACK; +} + +JSONRPC_STATUS CPlaylistOperations::Remove(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PLAYLIST::Id playlistId = GetPlaylist(parameterObject["playlistid"]); + if (playlistId == PLAYLIST::TYPE_PICTURE) + return FailedToExecute; + + int position = (int)parameterObject["position"].asInteger(); + if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == playlistId && + CServiceBroker::GetPlaylistPlayer().GetCurrentSong() == position) + return InvalidParams; + + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_PLAYLISTPLAYER_REMOVE, playlistId, position); + + return ACK; +} + +JSONRPC_STATUS CPlaylistOperations::Clear(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PLAYLIST::Id playlistId = GetPlaylist(parameterObject["playlistid"]); + CGUIWindowSlideShow *slideshow = NULL; + switch (playlistId) + { + case PLAYLIST::TYPE_MUSIC: + case PLAYLIST::TYPE_VIDEO: + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_PLAYLISTPLAYER_CLEAR, playlistId); + break; + + case PLAYLIST::TYPE_PICTURE: + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (!slideshow) + return FailedToExecute; + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTION, WINDOW_SLIDESHOW, -1, + static_cast<void*>(new CAction(ACTION_STOP))); + slideshow->Reset(); + break; + } + + return ACK; +} + +JSONRPC_STATUS CPlaylistOperations::Swap(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PLAYLIST::Id playlistId = GetPlaylist(parameterObject["playlistid"]); + if (playlistId == PLAYLIST::TYPE_PICTURE) + return FailedToExecute; + + auto tmpVec = new std::vector<int>(); + tmpVec->push_back(static_cast<int>(parameterObject["position1"].asInteger())); + tmpVec->push_back(static_cast<int>(parameterObject["position2"].asInteger())); + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_PLAYLISTPLAYER_SWAP, playlistId, -1, + static_cast<void*>(tmpVec)); + + return ACK; +} + +PLAYLIST::Id CPlaylistOperations::GetPlaylist(const CVariant& playlist) +{ + PLAYLIST::Id playlistId = playlist.asInteger(PLAYLIST::TYPE_NONE); + if (playlistId != PLAYLIST::TYPE_NONE) + return playlistId; + + return PLAYLIST::TYPE_NONE; +} + +JSONRPC_STATUS CPlaylistOperations::GetPropertyValue(PLAYLIST::Id playlistId, + const std::string& property, + CVariant& result) +{ + if (property == "type") + { + switch (playlistId) + { + case PLAYLIST::TYPE_MUSIC: + result = "audio"; + break; + + case PLAYLIST::TYPE_VIDEO: + result = "video"; + break; + + case PLAYLIST::TYPE_PICTURE: + result = "pictures"; + break; + + default: + result = "unknown"; + break; + } + } + else if (property == "size") + { + CFileItemList list; + CGUIWindowSlideShow *slideshow = NULL; + switch (playlistId) + { + case PLAYLIST::TYPE_MUSIC: + case PLAYLIST::TYPE_VIDEO: + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_PLAYLISTPLAYER_GET_ITEMS, playlistId, -1, + static_cast<void*>(&list)); + result = list.Size(); + break; + + case PLAYLIST::TYPE_PICTURE: + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow) + result = slideshow->NumSlides(); + else + result = 0; + break; + + default: + result = 0; + break; + } + } + else + return InvalidParams; + + return OK; +} + +bool CPlaylistOperations::HandleItemsParameter(PLAYLIST::Id playlistId, + const CVariant& itemParam, + CFileItemList& items) +{ + std::vector<CVariant> vecItems; + if (itemParam.isArray()) + vecItems.assign(itemParam.begin_array(), itemParam.end_array()); + else + vecItems.push_back(itemParam); + + bool success = false; + for (auto& itemIt : vecItems) + { + if (!CheckMediaParameter(playlistId, itemIt)) + continue; + + switch (playlistId) + { + case PLAYLIST::TYPE_VIDEO: + itemIt["media"] = "video"; + break; + case PLAYLIST::TYPE_MUSIC: + itemIt["media"] = "music"; + break; + case PLAYLIST::TYPE_PICTURE: + itemIt["media"] = "pictures"; + break; + } + + success |= FillFileItemList(itemIt, items); + } + + return success; +} diff --git a/xbmc/interfaces/json-rpc/PlaylistOperations.h b/xbmc/interfaces/json-rpc/PlaylistOperations.h new file mode 100644 index 0000000..a6a6f83 --- /dev/null +++ b/xbmc/interfaces/json-rpc/PlaylistOperations.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "FileItemHandler.h" +#include "JSONRPC.h" + +class CFileItemList; +class CVariant; + +namespace PLAYLIST +{ +using Id = int; +} // namespace PLAYLIST + +namespace JSONRPC +{ + class CPlaylistOperations : public CFileItemHandler + { + public: + static JSONRPC_STATUS GetPlaylists(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS GetItems(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Add(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Remove(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Insert(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Clear(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Swap(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + private: + static PLAYLIST::Id GetPlaylist(const CVariant& playlist); + static JSONRPC_STATUS GetPropertyValue(PLAYLIST::Id playlistId, + const std::string& property, + CVariant& result); + static bool CheckMediaParameter(PLAYLIST::Id playlistId, const CVariant& itemObject); + static bool HandleItemsParameter(PLAYLIST::Id playlistId, + const CVariant& itemParam, + CFileItemList& items); + }; +} diff --git a/xbmc/interfaces/json-rpc/ProfilesOperations.cpp b/xbmc/interfaces/json-rpc/ProfilesOperations.cpp new file mode 100644 index 0000000..adfb010 --- /dev/null +++ b/xbmc/interfaces/json-rpc/ProfilesOperations.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ProfilesOperations.h" + +#include "FileItem.h" +#include "GUIPassword.h" +#include "ServiceBroker.h" +#include "guilib/LocalizeStrings.h" +#include "messaging/ApplicationMessenger.h" +#include "profiles/ProfileManager.h" +#include "settings/SettingsComponent.h" +#include "utils/Digest.h" +#include "utils/Variant.h" + +using namespace JSONRPC; +using KODI::UTILITY::CDigest; + +JSONRPC_STATUS CProfilesOperations::GetProfiles(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + CFileItemList listItems; + + for (unsigned int i = 0; i < profileManager->GetNumberOfProfiles(); ++i) + { + const CProfile *profile = profileManager->GetProfile(i); + CFileItemPtr item(new CFileItem(profile->getName())); + item->SetArt("thumb", profile->getThumb()); + listItems.Add(item); + } + + HandleFileItemList("profileid", false, "profiles", listItems, parameterObject, result); + + for (CVariant::const_iterator_array propertyiter = parameterObject["properties"].begin_array(); propertyiter != parameterObject["properties"].end_array(); ++propertyiter) + { + if (propertyiter->isString() && + propertyiter->asString() == "lockmode") + { + for (CVariant::iterator_array profileiter = result["profiles"].begin_array(); profileiter != result["profiles"].end_array(); ++profileiter) + { + std::string profilename = (*profileiter)["label"].asString(); + int index = profileManager->GetProfileIndex(profilename); + const CProfile *profile = profileManager->GetProfile(index); + LockType locktype = LOCK_MODE_UNKNOWN; + if (index == 0) + locktype = profileManager->GetMasterProfile().getLockMode(); + else + locktype = profile->getLockMode(); + (*profileiter)["lockmode"] = locktype; + } + break; + } + } + return OK; +} + +JSONRPC_STATUS CProfilesOperations::GetCurrentProfile(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + const CProfile& currentProfile = profileManager->GetCurrentProfile(); + CVariant profileVariant = CVariant(CVariant::VariantTypeObject); + profileVariant["label"] = currentProfile.getName(); + for (CVariant::const_iterator_array propertyiter = parameterObject["properties"].begin_array(); propertyiter != parameterObject["properties"].end_array(); ++propertyiter) + { + if (propertyiter->isString()) + { + if (propertyiter->asString() == "lockmode") + profileVariant["lockmode"] = currentProfile.getLockMode(); + else if (propertyiter->asString() == "thumbnail") + profileVariant["thumbnail"] = currentProfile.getThumb(); + } + } + + result = profileVariant; + + return OK; +} + +JSONRPC_STATUS CProfilesOperations::LoadProfile(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + std::string profilename = parameterObject["profile"].asString(); + int index = profileManager->GetProfileIndex(profilename); + + if (index < 0) + return InvalidParams; + + // get the profile + const CProfile *profile = profileManager->GetProfile(index); + if (profile == NULL) + return InvalidParams; + + bool bPrompt = parameterObject["prompt"].asBoolean(); + bool bCanceled = false; + bool bLoadProfile = false; + + // if the profile does not require a password or + // the user is prompted and provides the correct password + // we can load the requested profile + if (profile->getLockMode() == LOCK_MODE_EVERYONE || + (bPrompt && g_passwordManager.IsProfileLockUnlocked(index, bCanceled, bPrompt))) + bLoadProfile = true; + else if (!bCanceled) // Password needed and user provided it + { + const CVariant &passwordObject = parameterObject["password"]; + const std::string& strToVerify = profile->getLockCode(); + std::string password = passwordObject["value"].asString(); + + // Create password hash from the provided password if md5 is not used + std::string md5pword2; + std::string encryption = passwordObject["encryption"].asString(); + if (encryption == "none") + md5pword2 = CDigest::Calculate(CDigest::Type::MD5, password); + else if (encryption == "md5") + md5pword2 = password; + + // Verify provided password + if (StringUtils::EqualsNoCase(strToVerify, md5pword2)) + bLoadProfile = true; + } + + if (bLoadProfile) + { + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_LOADPROFILE, index); + return ACK; + } + return InvalidParams; +} diff --git a/xbmc/interfaces/json-rpc/ProfilesOperations.h b/xbmc/interfaces/json-rpc/ProfilesOperations.h new file mode 100644 index 0000000..7f11b83 --- /dev/null +++ b/xbmc/interfaces/json-rpc/ProfilesOperations.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "FileItemHandler.h" +#include "JSONRPC.h" + +class CVariant; + +namespace JSONRPC +{ + class CProfilesOperations : CFileItemHandler + { + public: + static JSONRPC_STATUS GetProfiles(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetCurrentProfile(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS LoadProfile(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + }; +} diff --git a/xbmc/interfaces/json-rpc/SettingsOperations.cpp b/xbmc/interfaces/json-rpc/SettingsOperations.cpp new file mode 100644 index 0000000..09992f2 --- /dev/null +++ b/xbmc/interfaces/json-rpc/SettingsOperations.cpp @@ -0,0 +1,905 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingsOperations.h" + +#include "ServiceBroker.h" +#include "addons/Addon.h" +#include "addons/Skin.h" +#include "addons/addoninfo/AddonInfo.h" +#include "guilib/LocalizeStrings.h" +#include "settings/SettingAddon.h" +#include "settings/SettingControl.h" +#include "settings/SettingDateTime.h" +#include "settings/SettingPath.h" +#include "settings/SettingUtils.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/SkinSettings.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingDefinitions.h" +#include "settings/lib/SettingSection.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +using namespace JSONRPC; + +JSONRPC_STATUS CSettingsOperations::GetSections(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + SettingLevel level = ParseSettingLevel(parameterObject["level"].asString()); + bool listCategories = !parameterObject["properties"].empty() && parameterObject["properties"][0].asString() == "categories"; + + result["sections"] = CVariant(CVariant::VariantTypeArray); + + // apply the level filter + SettingSectionList allSections = CServiceBroker::GetSettingsComponent()->GetSettings()->GetSections(); + for (const auto& itSection : allSections) + { + SettingCategoryList categories = itSection->GetCategories(level); + if (categories.empty()) + continue; + + CVariant varSection(CVariant::VariantTypeObject); + if (!SerializeSettingSection(itSection, varSection)) + continue; + + if (listCategories) + { + varSection["categories"] = CVariant(CVariant::VariantTypeArray); + for (const auto& itCategory : categories) + { + CVariant varCategory(CVariant::VariantTypeObject); + if (!SerializeSettingCategory(itCategory, varCategory)) + continue; + + varSection["categories"].push_back(varCategory); + } + } + + result["sections"].push_back(varSection); + } + + return OK; +} + +JSONRPC_STATUS CSettingsOperations::GetCategories(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + SettingLevel level = ParseSettingLevel(parameterObject["level"].asString()); + std::string strSection = parameterObject["section"].asString(); + bool listSettings = !parameterObject["properties"].empty() && parameterObject["properties"][0].asString() == "settings"; + + std::vector<SettingSectionPtr> sections; + if (!strSection.empty()) + { + SettingSectionPtr section = CServiceBroker::GetSettingsComponent()->GetSettings()->GetSection(strSection); + if (section == NULL) + return InvalidParams; + + sections.push_back(section); + } + else + sections = CServiceBroker::GetSettingsComponent()->GetSettings()->GetSections(); + + result["categories"] = CVariant(CVariant::VariantTypeArray); + + for (const auto& itSection : sections) + { + SettingCategoryList categories = itSection->GetCategories(level); + for (const auto& itCategory : categories) + { + CVariant varCategory(CVariant::VariantTypeObject); + if (!SerializeSettingCategory(itCategory, varCategory)) + continue; + + if (listSettings) + { + varCategory["groups"] = CVariant(CVariant::VariantTypeArray); + + SettingGroupList groups = itCategory->GetGroups(level); + for (const auto& itGroup : groups) + { + CVariant varGroup(CVariant::VariantTypeObject); + if (!SerializeSettingGroup(itGroup, varGroup)) + continue; + + varGroup["settings"] = CVariant(CVariant::VariantTypeArray); + SettingList settings = itGroup->GetSettings(level); + for (const auto& itSetting : settings) + { + if (itSetting->IsVisible()) + { + CVariant varSetting(CVariant::VariantTypeObject); + if (!SerializeSetting(itSetting, varSetting)) + continue; + + varGroup["settings"].push_back(varSetting); + } + } + + varCategory["groups"].push_back(varGroup); + } + } + + result["categories"].push_back(varCategory); + } + } + + return OK; +} + +JSONRPC_STATUS CSettingsOperations::GetSettings(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + SettingLevel level = ParseSettingLevel(parameterObject["level"].asString()); + const CVariant &filter = parameterObject["filter"]; + bool doFilter = filter.isMember("section") && filter.isMember("category"); + std::string strSection, strCategory; + if (doFilter) + { + strSection = filter["section"].asString(); + strCategory = filter["category"].asString(); + } + + std::vector<SettingSectionPtr> sections; + + if (doFilter) + { + SettingSectionPtr section = CServiceBroker::GetSettingsComponent()->GetSettings()->GetSection(strSection); + if (section == NULL) + return InvalidParams; + + sections.push_back(section); + } + else + sections = CServiceBroker::GetSettingsComponent()->GetSettings()->GetSections(); + + result["settings"] = CVariant(CVariant::VariantTypeArray); + + for (const auto& itSection : sections) + { + SettingCategoryList categories = itSection->GetCategories(level); + bool found = !doFilter; + for (const auto& itCategory : categories) + { + if (!doFilter || StringUtils::EqualsNoCase(itCategory->GetId(), strCategory)) + { + SettingGroupList groups = itCategory->GetGroups(level); + for (const auto& itGroup : groups) + { + SettingList settings = itGroup->GetSettings(level); + for (const auto& itSetting : settings) + { + if (itSetting->IsVisible()) + { + CVariant varSetting(CVariant::VariantTypeObject); + if (!SerializeSetting(itSetting, varSetting)) + continue; + + result["settings"].push_back(varSetting); + } + } + } + found = true; + + if (doFilter) + break; + } + } + + if (doFilter && !found) + return InvalidParams; + } + + return OK; +} + +JSONRPC_STATUS CSettingsOperations::GetSettingValue(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string settingId = parameterObject["setting"].asString(); + + SettingPtr setting = CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(settingId); + if (setting == NULL || + !setting->IsVisible()) + return InvalidParams; + + CVariant value; + switch (setting->GetType()) + { + case SettingType::Boolean: + value = std::static_pointer_cast<CSettingBool>(setting)->GetValue(); + break; + + case SettingType::Integer: + value = std::static_pointer_cast<CSettingInt>(setting)->GetValue(); + break; + + case SettingType::Number: + value = std::static_pointer_cast<CSettingNumber>(setting)->GetValue(); + break; + + case SettingType::String: + value = std::static_pointer_cast<CSettingString>(setting)->GetValue(); + break; + + case SettingType::List: + { + SerializeSettingListValues(CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(settingId), value); + break; + } + + case SettingType::Unknown: + case SettingType::Action: + default: + return InvalidParams; + } + + result["value"] = value; + + return OK; +} + +JSONRPC_STATUS CSettingsOperations::SetSettingValue(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string settingId = parameterObject["setting"].asString(); + CVariant value = parameterObject["value"]; + + SettingPtr setting = CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(settingId); + if (setting == NULL || + !setting->IsVisible()) + return InvalidParams; + + switch (setting->GetType()) + { + case SettingType::Boolean: + if (!value.isBoolean()) + return InvalidParams; + + result = std::static_pointer_cast<CSettingBool>(setting)->SetValue(value.asBoolean()); + break; + + case SettingType::Integer: + if (!value.isInteger() && !value.isUnsignedInteger()) + return InvalidParams; + + result = std::static_pointer_cast<CSettingInt>(setting)->SetValue((int)value.asInteger()); + break; + + case SettingType::Number: + if (!value.isDouble()) + return InvalidParams; + + result = std::static_pointer_cast<CSettingNumber>(setting)->SetValue(value.asDouble()); + break; + + case SettingType::String: + if (!value.isString()) + return InvalidParams; + + result = std::static_pointer_cast<CSettingString>(setting)->SetValue(value.asString()); + break; + + case SettingType::List: + { + if (!value.isArray()) + return InvalidParams; + + std::vector<CVariant> values; + for (CVariant::const_iterator_array itValue = value.begin_array(); itValue != value.end_array(); ++itValue) + values.push_back(*itValue); + + result = CServiceBroker::GetSettingsComponent()->GetSettings()->SetList(settingId, values); + break; + } + + case SettingType::Unknown: + case SettingType::Action: + default: + return InvalidParams; + } + + return OK; +} + +JSONRPC_STATUS CSettingsOperations::ResetSettingValue(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string settingId = parameterObject["setting"].asString(); + + SettingPtr setting = CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(settingId); + if (setting == NULL || + !setting->IsVisible()) + return InvalidParams; + + switch (setting->GetType()) + { + case SettingType::Boolean: + case SettingType::Integer: + case SettingType::Number: + case SettingType::String: + case SettingType::List: + setting->Reset(); + break; + + case SettingType::Unknown: + case SettingType::Action: + default: + return InvalidParams; + } + + return ACK; +} + +SettingLevel CSettingsOperations::ParseSettingLevel(const std::string &strLevel) +{ + if (StringUtils::EqualsNoCase(strLevel, "basic")) + return SettingLevel::Basic; + if (StringUtils::EqualsNoCase(strLevel, "advanced")) + return SettingLevel::Advanced; + if (StringUtils::EqualsNoCase(strLevel, "expert")) + return SettingLevel::Expert; + + return SettingLevel::Standard; +} + +bool CSettingsOperations::SerializeISetting(const std::shared_ptr<const ISetting>& setting, + CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["id"] = setting->GetId(); + + return true; +} + +bool CSettingsOperations::SerializeSettingSection( + const std::shared_ptr<const CSettingSection>& setting, CVariant& obj) +{ + if (!SerializeISetting(setting, obj)) + return false; + + obj["label"] = g_localizeStrings.Get(setting->GetLabel()); + if (setting->GetHelp() >= 0) + obj["help"] = g_localizeStrings.Get(setting->GetHelp()); + + return true; +} + +bool CSettingsOperations::SerializeSettingCategory( + const std::shared_ptr<const CSettingCategory>& setting, CVariant& obj) +{ + if (!SerializeISetting(setting, obj)) + return false; + + obj["label"] = g_localizeStrings.Get(setting->GetLabel()); + if (setting->GetHelp() >= 0) + obj["help"] = g_localizeStrings.Get(setting->GetHelp()); + + return true; +} + +bool CSettingsOperations::SerializeSettingGroup(const std::shared_ptr<const CSettingGroup>& setting, + CVariant& obj) +{ + return SerializeISetting(setting, obj); +} + +bool CSettingsOperations::SerializeSetting(const std::shared_ptr<const CSetting>& setting, + CVariant& obj) +{ + if (!SerializeISetting(setting, obj)) + return false; + + obj["label"] = g_localizeStrings.Get(setting->GetLabel()); + if (setting->GetHelp() >= 0) + obj["help"] = g_localizeStrings.Get(setting->GetHelp()); + + switch (setting->GetLevel()) + { + case SettingLevel::Basic: + obj["level"] = "basic"; + break; + + case SettingLevel::Standard: + obj["level"] = "standard"; + break; + + case SettingLevel::Advanced: + obj["level"] = "advanced"; + break; + + case SettingLevel::Expert: + obj["level"] = "expert"; + break; + + default: + return false; + } + + obj["enabled"] = setting->IsEnabled(); + obj["parent"] = setting->GetParent(); + + obj["control"] = CVariant(CVariant::VariantTypeObject); + if (!SerializeSettingControl(setting->GetControl(), obj["control"])) + return false; + + switch (setting->GetType()) + { + case SettingType::Boolean: + obj["type"] = "boolean"; + if (!SerializeSettingBool(std::static_pointer_cast<const CSettingBool>(setting), obj)) + return false; + break; + + case SettingType::Integer: + obj["type"] = "integer"; + if (!SerializeSettingInt(std::static_pointer_cast<const CSettingInt>(setting), obj)) + return false; + break; + + case SettingType::Number: + obj["type"] = "number"; + if (!SerializeSettingNumber(std::static_pointer_cast<const CSettingNumber>(setting), obj)) + return false; + break; + + case SettingType::String: + obj["type"] = "string"; + if (!SerializeSettingString(std::static_pointer_cast<const CSettingString>(setting), obj)) + return false; + break; + + case SettingType::Action: + obj["type"] = "action"; + if (!SerializeSettingAction(std::static_pointer_cast<const CSettingAction>(setting), obj)) + return false; + break; + + case SettingType::List: + obj["type"] = "list"; + if (!SerializeSettingList(std::static_pointer_cast<const CSettingList>(setting), obj)) + return false; + break; + + default: + return false; + } + + return true; +} + +bool CSettingsOperations::SerializeSettingBool(const std::shared_ptr<const CSettingBool>& setting, + CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["value"] = setting->GetValue(); + obj["default"] = setting->GetDefault(); + + return true; +} + +bool CSettingsOperations::SerializeSettingInt(const std::shared_ptr<const CSettingInt>& setting, + CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["default"] = setting->GetDefault(); + + switch (setting->GetOptionsType()) + { + case SettingOptionsType::StaticTranslatable: + { + obj["options"] = CVariant(CVariant::VariantTypeArray); + const TranslatableIntegerSettingOptions& options = setting->GetTranslatableOptions(); + for (const auto& itOption : options) + { + CVariant varOption(CVariant::VariantTypeObject); + varOption["label"] = g_localizeStrings.Get(itOption.label); + varOption["value"] = itOption.value; + obj["options"].push_back(varOption); + } + break; + } + + case SettingOptionsType::Static: + { + obj["options"] = CVariant(CVariant::VariantTypeArray); + const IntegerSettingOptions& options = setting->GetOptions(); + for (const auto& itOption : options) + { + CVariant varOption(CVariant::VariantTypeObject); + varOption["label"] = itOption.label; + varOption["value"] = itOption.value; + obj["options"].push_back(varOption); + } + break; + } + + case SettingOptionsType::Dynamic: + { + obj["options"] = CVariant(CVariant::VariantTypeArray); + IntegerSettingOptions options = std::const_pointer_cast<CSettingInt>(setting)->UpdateDynamicOptions(); + for (const auto& itOption : options) + { + CVariant varOption(CVariant::VariantTypeObject); + varOption["label"] = itOption.label; + varOption["value"] = itOption.value; + obj["options"].push_back(varOption); + } + break; + } + + case SettingOptionsType::Unknown: + default: + obj["minimum"] = setting->GetMinimum(); + obj["step"] = setting->GetStep(); + obj["maximum"] = setting->GetMaximum(); + break; + } + + // this must be done after potentially calling CSettingInt::UpdateDynamicOptions() because it can + // change the value of the setting + obj["value"] = setting->GetValue(); + + return true; +} + +bool CSettingsOperations::SerializeSettingNumber( + const std::shared_ptr<const CSettingNumber>& setting, CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["value"] = setting->GetValue(); + obj["default"] = setting->GetDefault(); + + obj["minimum"] = setting->GetMinimum(); + obj["step"] = setting->GetStep(); + obj["maximum"] = setting->GetMaximum(); + + return true; +} + +bool CSettingsOperations::SerializeSettingString( + const std::shared_ptr<const CSettingString>& setting, CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["default"] = setting->GetDefault(); + + obj["allowempty"] = setting->AllowEmpty(); + obj["allownewoption"] = setting->AllowNewOption(); + + switch (setting->GetOptionsType()) + { + case SettingOptionsType::StaticTranslatable: + { + obj["options"] = CVariant(CVariant::VariantTypeArray); + const TranslatableStringSettingOptions& options = setting->GetTranslatableOptions(); + for (const auto& itOption : options) + { + CVariant varOption(CVariant::VariantTypeObject); + varOption["label"] = g_localizeStrings.Get(itOption.first); + varOption["value"] = itOption.second; + obj["options"].push_back(varOption); + } + break; + } + + case SettingOptionsType::Static: + { + obj["options"] = CVariant(CVariant::VariantTypeArray); + const StringSettingOptions& options = setting->GetOptions(); + for (const auto& itOption : options) + { + CVariant varOption(CVariant::VariantTypeObject); + varOption["label"] = itOption.label; + varOption["value"] = itOption.value; + obj["options"].push_back(varOption); + } + break; + } + + case SettingOptionsType::Dynamic: + { + obj["options"] = CVariant(CVariant::VariantTypeArray); + StringSettingOptions options = std::const_pointer_cast<CSettingString>(setting)->UpdateDynamicOptions(); + for (const auto& itOption : options) + { + CVariant varOption(CVariant::VariantTypeObject); + varOption["label"] = itOption.label; + varOption["value"] = itOption.value; + obj["options"].push_back(varOption); + } + break; + } + + case SettingOptionsType::Unknown: + default: + break; + } + + // this must be done after potentially calling CSettingString::UpdateDynamicOptions() because it + // can change the value of the setting + obj["value"] = setting->GetValue(); + + std::shared_ptr<const ISettingControl> control = setting->GetControl(); + if (control->GetFormat() == "path") + { + if (!SerializeSettingPath(std::static_pointer_cast<const CSettingPath>(setting), obj)) + return false; + } + if (control->GetFormat() == "addon") + { + if (!SerializeSettingAddon(std::static_pointer_cast<const CSettingAddon>(setting), obj)) + return false; + } + if (control->GetFormat() == "date") + { + if (!SerializeSettingDate(std::static_pointer_cast<const CSettingDate>(setting), obj)) + return false; + } + if (control->GetFormat() == "time") + { + if (!SerializeSettingTime(std::static_pointer_cast<const CSettingTime>(setting), obj)) + return false; + } + + return true; +} + +bool CSettingsOperations::SerializeSettingAction( + const std::shared_ptr<const CSettingAction>& setting, CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["data"] = setting->GetData(); + + return true; +} + +bool CSettingsOperations::SerializeSettingList(const std::shared_ptr<const CSettingList>& setting, + CVariant& obj) +{ + if (setting == NULL || + !SerializeSetting(setting->GetDefinition(), obj["definition"])) + return false; + + SerializeSettingListValues(CSettingUtils::GetList(setting), obj["value"]); + SerializeSettingListValues(CSettingUtils::ListToValues(setting, setting->GetDefault()), obj["default"]); + + obj["elementtype"] = obj["definition"]["type"]; + obj["delimiter"] = setting->GetDelimiter(); + obj["minimumItems"] = setting->GetMinimumItems(); + obj["maximumItems"] = setting->GetMaximumItems(); + + return true; +} + +bool CSettingsOperations::SerializeSettingPath(const std::shared_ptr<const CSettingPath>& setting, + CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["type"] = "path"; + obj["writable"] = setting->Writable(); + obj["sources"] = setting->GetSources(); + + return true; +} + +bool CSettingsOperations::SerializeSettingAddon(const std::shared_ptr<const CSettingAddon>& setting, + CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["type"] = "addon"; + obj["addontype"] = ADDON::CAddonInfo::TranslateType(setting->GetAddonType()); + + return true; +} + +bool CSettingsOperations::SerializeSettingDate(const std::shared_ptr<const CSettingDate>& setting, + CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["type"] = "date"; + + return true; +} + +bool CSettingsOperations::SerializeSettingTime(const std::shared_ptr<const CSettingTime>& setting, + CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["type"] = "time"; + + return true; +} + +bool CSettingsOperations::SerializeSettingControl( + const std::shared_ptr<const ISettingControl>& control, CVariant& obj) +{ + if (control == NULL) + return false; + + const std::string& type = control->GetType(); + obj["type"] = type; + obj["format"] = control->GetFormat(); + obj["delayed"] = control->GetDelayed(); + + if (type == "spinner") + { + std::shared_ptr<const CSettingControlSpinner> spinner = std::static_pointer_cast<const CSettingControlSpinner>(control); + if (spinner->GetFormatLabel() >= 0) + obj["formatlabel"] = g_localizeStrings.Get(spinner->GetFormatLabel()); + else if (!spinner->GetFormatString().empty() && spinner->GetFormatString() != "{:d}") + obj["formatlabel"] = spinner->GetFormatString(); + if (spinner->GetMinimumLabel() >= 0) + obj["minimumlabel"] = g_localizeStrings.Get(spinner->GetMinimumLabel()); + } + else if (type == "edit") + { + std::shared_ptr<const CSettingControlEdit> edit = std::static_pointer_cast<const CSettingControlEdit>(control); + obj["hidden"] = edit->IsHidden(); + obj["verifynewvalue"] = edit->VerifyNewValue(); + if (edit->GetHeading() >= 0) + obj["heading"] = g_localizeStrings.Get(edit->GetHeading()); + } + else if (type == "button") + { + std::shared_ptr<const CSettingControlButton> button = std::static_pointer_cast<const CSettingControlButton>(control); + if (button->GetHeading() >= 0) + obj["heading"] = g_localizeStrings.Get(button->GetHeading()); + } + else if (type == "list") + { + std::shared_ptr<const CSettingControlList> list = std::static_pointer_cast<const CSettingControlList>(control); + if (list->GetHeading() >= 0) + obj["heading"] = g_localizeStrings.Get(list->GetHeading()); + obj["multiselect"] = list->CanMultiSelect(); + } + else if (type == "slider") + { + std::shared_ptr<const CSettingControlSlider> slider = std::static_pointer_cast<const CSettingControlSlider>(control); + if (slider->GetHeading() >= 0) + obj["heading"] = g_localizeStrings.Get(slider->GetHeading()); + obj["popup"] = slider->UsePopup(); + if (slider->GetFormatLabel() >= 0) + obj["formatlabel"] = g_localizeStrings.Get(slider->GetFormatLabel()); + else + obj["formatlabel"] = slider->GetFormatString(); + } + else if (type == "range") + { + std::shared_ptr<const CSettingControlRange> range = std::static_pointer_cast<const CSettingControlRange>(control); + if (range->GetFormatLabel() >= 0) + obj["formatlabel"] = g_localizeStrings.Get(range->GetFormatLabel()); + else + obj["formatlabel"] = ""; + if (range->GetValueFormatLabel() >= 0) + obj["formatvalue"] = g_localizeStrings.Get(range->GetValueFormatLabel()); + else + obj["formatvalue"] = range->GetValueFormat(); + } + else if (type != "toggle" && type != "label") + return false; + + return true; +} + +void CSettingsOperations::SerializeSettingListValues(const std::vector<CVariant> &values, CVariant &obj) +{ + obj = CVariant(CVariant::VariantTypeArray); + for (const auto& itValue : values) + obj.push_back(itValue); +} + +JSONRPC_STATUS CSettingsOperations::GetSkinSettings(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) +{ + const std::set<ADDON::CSkinSettingPtr> settings = CSkinSettings::GetInstance().GetSettings(); + CVariant varSettings(CVariant::VariantTypeArray); + + for (const auto& setting : settings) + { + CVariant varSetting(CVariant::VariantTypeObject); + varSetting["id"] = setting->name; + + if (setting->GetType() == "bool") + { + varSetting["value"] = std::static_pointer_cast<ADDON::CSkinSettingBool>(setting)->value; + varSetting["type"] = "boolean"; + } + else if (setting->GetType() == "string") + { + varSetting["value"] = std::static_pointer_cast<ADDON::CSkinSettingString>(setting)->value; + varSetting["type"] = setting->GetType(); + } + else + continue; + + varSettings.push_back(varSetting); + } + + result["skin"] = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString( + CSettings::SETTING_LOOKANDFEEL_SKIN); + result["settings"] = varSettings; + return OK; +} + +JSONRPC_STATUS CSettingsOperations::GetSkinSettingValue(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) +{ + const std::string settingId = parameterObject["setting"].asString(); + ADDON::CSkinSettingPtr setting = CSkinSettings::GetInstance().GetSetting(settingId); + + if (setting == nullptr) + return InvalidParams; + + CVariant value; + if (setting->GetType() == "string") + value = std::static_pointer_cast<ADDON::CSkinSettingString>(setting)->value; + else if (setting->GetType() == "bool") + value = std::static_pointer_cast<ADDON::CSkinSettingBool>(setting)->value; + else + return InvalidParams; + + result["value"] = value; + return OK; +} + +JSONRPC_STATUS CSettingsOperations::SetSkinSettingValue(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) +{ + const std::string settingId = parameterObject["setting"].asString(); + ADDON::CSkinSettingPtr setting = CSkinSettings::GetInstance().GetSetting(settingId); + + if (setting == nullptr) + return InvalidParams; + + CVariant value = parameterObject["value"]; + if (setting->GetType() == "string") + { + if (!value.isString()) + return InvalidParams; + + result = std::static_pointer_cast<ADDON::CSkinSettingString>(setting)->value = value.asString(); + } + else if (setting->GetType() == "bool") + { + if (!value.isBoolean()) + return InvalidParams; + + result = std::static_pointer_cast<ADDON::CSkinSettingBool>(setting)->value = value.asBoolean(); + } + else + { + return InvalidParams; + } + + return OK; +} diff --git a/xbmc/interfaces/json-rpc/SettingsOperations.h b/xbmc/interfaces/json-rpc/SettingsOperations.h new file mode 100644 index 0000000..6267e73 --- /dev/null +++ b/xbmc/interfaces/json-rpc/SettingsOperations.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPC.h" +#include "settings/lib/SettingLevel.h" + +#include <vector> + +class CVariant; +class ISetting; +class CSettingSection; +class CSettingCategory; +class CSettingGroup; +class CSetting; +class CSettingBool; +class CSettingInt; +class CSettingNumber; +class CSettingString; +class CSettingAction; +class CSettingList; +class CSettingPath; +class CSettingAddon; +class CSettingDate; +class CSettingTime; +class ISettingControl; + +namespace JSONRPC +{ + class CSettingsOperations + { + public: + static JSONRPC_STATUS GetSections(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetCategories(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetSettings(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS GetSettingValue(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetSettingValue(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS ResetSettingValue(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS GetSkinSettings(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result); + static JSONRPC_STATUS GetSkinSettingValue(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result); + static JSONRPC_STATUS SetSkinSettingValue(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result); + + private: + static SettingLevel ParseSettingLevel(const std::string &strLevel); + + static bool SerializeISetting(const std::shared_ptr<const ISetting>& setting, CVariant& obj); + static bool SerializeSettingSection(const std::shared_ptr<const CSettingSection>& setting, + CVariant& obj); + static bool SerializeSettingCategory(const std::shared_ptr<const CSettingCategory>& setting, + CVariant& obj); + static bool SerializeSettingGroup(const std::shared_ptr<const CSettingGroup>& setting, + CVariant& obj); + static bool SerializeSetting(const std::shared_ptr<const CSetting>& setting, CVariant& obj); + static bool SerializeSettingBool(const std::shared_ptr<const CSettingBool>& setting, + CVariant& obj); + static bool SerializeSettingInt(const std::shared_ptr<const CSettingInt>& setting, + CVariant& obj); + static bool SerializeSettingNumber(const std::shared_ptr<const CSettingNumber>& setting, + CVariant& obj); + static bool SerializeSettingString(const std::shared_ptr<const CSettingString>& setting, + CVariant& obj); + static bool SerializeSettingAction(const std::shared_ptr<const CSettingAction>& setting, + CVariant& obj); + static bool SerializeSettingList(const std::shared_ptr<const CSettingList>& setting, + CVariant& obj); + static bool SerializeSettingPath(const std::shared_ptr<const CSettingPath>& setting, + CVariant& obj); + static bool SerializeSettingAddon(const std::shared_ptr<const CSettingAddon>& setting, + CVariant& obj); + static bool SerializeSettingDate(const std::shared_ptr<const CSettingDate>& setting, + CVariant& obj); + static bool SerializeSettingTime(const std::shared_ptr<const CSettingTime>& setting, + CVariant& obj); + static bool SerializeSettingControl(const std::shared_ptr<const ISettingControl>& control, + CVariant& obj); + + static void SerializeSettingListValues(const std::vector<CVariant> &values, CVariant &obj); + }; +} diff --git a/xbmc/interfaces/json-rpc/SystemOperations.cpp b/xbmc/interfaces/json-rpc/SystemOperations.cpp new file mode 100644 index 0000000..b4d508c --- /dev/null +++ b/xbmc/interfaces/json-rpc/SystemOperations.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SystemOperations.h" + +#include "ServiceBroker.h" +#include "interfaces/builtins/Builtins.h" +#include "messaging/ApplicationMessenger.h" +#include "powermanagement/PowerManager.h" +#include "utils/Variant.h" + +using namespace JSONRPC; + +JSONRPC_STATUS CSystemOperations::GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVariant properties = CVariant(CVariant::VariantTypeObject); + for (unsigned int index = 0; index < parameterObject["properties"].size(); index++) + { + std::string propertyName = parameterObject["properties"][index].asString(); + CVariant property; + JSONRPC_STATUS ret; + if ((ret = GetPropertyValue(client->GetPermissionFlags(), propertyName, property)) != OK) + return ret; + + properties[propertyName] = property; + } + + result = properties; + + return OK; +} + +JSONRPC_STATUS CSystemOperations::EjectOpticalDrive(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return CBuiltins::GetInstance().Execute("EjectTray") == 0 ? ACK : FailedToExecute; +} + +JSONRPC_STATUS CSystemOperations::Shutdown(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (CServiceBroker::GetPowerManager().CanPowerdown()) + { + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_POWERDOWN); + return ACK; + } + else + return FailedToExecute; +} + +JSONRPC_STATUS CSystemOperations::Suspend(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (CServiceBroker::GetPowerManager().CanSuspend()) + { + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SUSPEND); + return ACK; + } + else + return FailedToExecute; +} + +JSONRPC_STATUS CSystemOperations::Hibernate(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (CServiceBroker::GetPowerManager().CanHibernate()) + { + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_HIBERNATE); + return ACK; + } + else + return FailedToExecute; +} + +JSONRPC_STATUS CSystemOperations::Reboot(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (CServiceBroker::GetPowerManager().CanReboot()) + { + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_RESTART); + return ACK; + } + else + return FailedToExecute; +} + +JSONRPC_STATUS CSystemOperations::GetPropertyValue(int permissions, const std::string &property, CVariant &result) +{ + if (property == "canshutdown") + result = CServiceBroker::GetPowerManager().CanPowerdown() && (permissions & ControlPower); + else if (property == "cansuspend") + result = CServiceBroker::GetPowerManager().CanSuspend() && (permissions & ControlPower); + else if (property == "canhibernate") + result = CServiceBroker::GetPowerManager().CanHibernate() && (permissions & ControlPower); + else if (property == "canreboot") + result = CServiceBroker::GetPowerManager().CanReboot() && (permissions & ControlPower); + else + return InvalidParams; + + return OK; +} diff --git a/xbmc/interfaces/json-rpc/SystemOperations.h b/xbmc/interfaces/json-rpc/SystemOperations.h new file mode 100644 index 0000000..1bc1ce2 --- /dev/null +++ b/xbmc/interfaces/json-rpc/SystemOperations.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPC.h" + +class CVariant; + +namespace JSONRPC +{ + class CSystemOperations + { + public: + static JSONRPC_STATUS GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS EjectOpticalDrive(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS Shutdown(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Suspend(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Hibernate(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Reboot(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + private: + static JSONRPC_STATUS GetPropertyValue(int permissions, const std::string &property, CVariant &result); + }; +} diff --git a/xbmc/interfaces/json-rpc/TextureOperations.cpp b/xbmc/interfaces/json-rpc/TextureOperations.cpp new file mode 100644 index 0000000..326093b --- /dev/null +++ b/xbmc/interfaces/json-rpc/TextureOperations.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "TextureOperations.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "TextureCache.h" +#include "TextureDatabase.h" +#include "utils/Variant.h" + +using namespace JSONRPC; + +JSONRPC_STATUS CTextureOperations::GetTextures(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CFileItemList listItems; + + CTextureDatabase db; + if (!db.Open()) + return InternalError; + + CDatabase::Filter dbFilter; + const CVariant &filter = parameterObject["filter"]; + if (filter.isObject()) + { + CVariant xspObj(CVariant::VariantTypeObject); + + if (filter.isMember("field")) + { + xspObj["and"] = CVariant(CVariant::VariantTypeArray); + xspObj["and"].push_back(filter); + } + else + xspObj = filter; + + // decipher the rules + CDatabaseQueryRuleCombination rule; + if (!rule.Load(xspObj, &db)) + return InvalidParams; + + dbFilter.AppendWhere(rule.GetWhereClause(db, "")); + } + + // fetch textures from the database + CVariant items = CVariant(CVariant::VariantTypeArray); + if (!db.GetTextures(items, dbFilter)) + return InternalError; + + // return only what was asked for, plus textureid + CVariant prop = parameterObject["properties"]; + prop.push_back("textureid"); + if (!items.empty() && prop.isArray()) + { + std::set<std::string> fields; + CVariant &item = items[0]; + for (CVariant::const_iterator_map field = item.begin_map(); field != item.end_map(); ++field) + { + if (std::find(prop.begin_array(), prop.end_array(), field->first) == prop.end_array()) + fields.insert(field->first); + } + // erase these fields + for (CVariant::iterator_array item = items.begin_array(); item != items.end_array(); ++item) + { + for (const auto& i : fields) + item->erase(i); + } + if (fields.find("url") == fields.end()) + { + // wrap cached url to something retrieval from Files.GetFiles() + for (CVariant::iterator_array item = items.begin_array(); item != items.end_array(); ++item) + { + CVariant &cachedUrl = (*item)["url"]; + cachedUrl = CTextureUtils::GetWrappedImageURL(cachedUrl.asString()); + } + } + } + + result["textures"] = items; + return OK; +} + +JSONRPC_STATUS CTextureOperations::RemoveTexture(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["textureid"].asInteger(); + + if (!CServiceBroker::GetTextureCache()->ClearCachedImage(id)) + return InvalidParams; + + return ACK; +} diff --git a/xbmc/interfaces/json-rpc/TextureOperations.h b/xbmc/interfaces/json-rpc/TextureOperations.h new file mode 100644 index 0000000..63a53a3 --- /dev/null +++ b/xbmc/interfaces/json-rpc/TextureOperations.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPC.h" + +class CVariant; + +namespace JSONRPC +{ + class CTextureOperations + { + public: + static JSONRPC_STATUS GetTextures(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS RemoveTexture(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + }; +} diff --git a/xbmc/interfaces/json-rpc/VideoLibrary.cpp b/xbmc/interfaces/json-rpc/VideoLibrary.cpp new file mode 100644 index 0000000..db152cd --- /dev/null +++ b/xbmc/interfaces/json-rpc/VideoLibrary.cpp @@ -0,0 +1,1385 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "VideoLibrary.h" + +#include "FileItem.h" +#include "PVROperations.h" +#include "ServiceBroker.h" +#include "TextureDatabase.h" +#include "Util.h" +#include "messaging/ApplicationMessenger.h" +#include "utils/SortUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "video/VideoDatabase.h" +#include "video/VideoDbUrl.h" +#include "video/VideoLibraryQueue.h" + +using namespace JSONRPC; + +JSONRPC_STATUS CVideoLibrary::GetMovies(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + SortDescription sorting; + ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd); + if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes)) + return InvalidParams; + + CVideoDbUrl videoUrl; + if (!videoUrl.FromString("videodb://movies/titles/")) + return InternalError; + + int genreID = -1, year = -1, setID = 0; + const CVariant &filter = parameterObject["filter"]; + if (filter.isMember("genreid")) + genreID = (int)filter["genreid"].asInteger(); + else if (filter.isMember("genre")) + videoUrl.AddOption("genre", filter["genre"].asString()); + else if (filter.isMember("year")) + year = (int)filter["year"].asInteger(); + else if (filter.isMember("actor")) + videoUrl.AddOption("actor", filter["actor"].asString()); + else if (filter.isMember("director")) + videoUrl.AddOption("director", filter["director"].asString()); + else if (filter.isMember("studio")) + videoUrl.AddOption("studio", filter["studio"].asString()); + else if (filter.isMember("country")) + videoUrl.AddOption("country", filter["country"].asString()); + else if (filter.isMember("setid")) + setID = (int)filter["setid"].asInteger(); + else if (filter.isMember("set")) + videoUrl.AddOption("set", filter["set"].asString()); + else if (filter.isMember("tag")) + videoUrl.AddOption("tag", filter["tag"].asString()); + else if (filter.isObject()) + { + std::string xsp; + if (!GetXspFiltering("movies", filter, xsp)) + return InvalidParams; + + videoUrl.AddOption("xsp", xsp); + } + + // setID must not be -1 otherwise GetMoviesNav() will return sets + if (setID < 0) + setID = 0; + + CFileItemList items; + if (!videodatabase.GetMoviesNav(videoUrl.ToString(), items, genreID, year, -1, -1, -1, -1, setID, -1, sorting, RequiresAdditionalDetails(MediaTypeMovie, parameterObject))) + return InvalidParams; + + return HandleItems("movieid", "movies", items, parameterObject, result, false); +} + +JSONRPC_STATUS CVideoLibrary::GetMovieDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["movieid"].asInteger(); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + if (!videodatabase.GetMovieInfo("", infos, id, RequiresAdditionalDetails(MediaTypeMovie, parameterObject)) || infos.m_iDbId <= 0) + return InvalidParams; + + HandleFileItem("movieid", true, "moviedetails", CFileItemPtr(new CFileItem(infos)), parameterObject, parameterObject["properties"], result, false); + return OK; +} + +JSONRPC_STATUS CVideoLibrary::GetMovieSets(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CFileItemList items; + if (!videodatabase.GetSetsNav("videodb://movies/sets/", items, VideoDbContentType::MOVIES)) + return InternalError; + + HandleFileItemList("setid", false, "sets", items, parameterObject, result); + return OK; +} + +JSONRPC_STATUS CVideoLibrary::GetMovieSetDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["setid"].asInteger(); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + // Get movie set details + CVideoInfoTag infos; + if (!videodatabase.GetSetInfo(id, infos) || infos.m_iDbId <= 0) + return InvalidParams; + + HandleFileItem("setid", false, "setdetails", CFileItemPtr(new CFileItem(infos)), parameterObject, parameterObject["properties"], result, false); + + // Get movies from the set + CFileItemList items; + if (!videodatabase.GetMoviesNav("videodb://movies/titles/", items, -1, -1, -1, -1, -1, -1, id, -1, SortDescription(), RequiresAdditionalDetails(MediaTypeMovie, parameterObject["movies"]))) + return InternalError; + + return HandleItems("movieid", "movies", items, parameterObject["movies"], result["setdetails"], true); +} + +JSONRPC_STATUS CVideoLibrary::GetTVShows(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + SortDescription sorting; + ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd); + if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes)) + return InvalidParams; + + CVideoDbUrl videoUrl; + if (!videoUrl.FromString("videodb://tvshows/titles/")) + return InternalError; + + const CVariant &filter = parameterObject["filter"]; + if (filter.isMember("genreid")) + videoUrl.AddOption("genreid", (int)filter["genreid"].asInteger()); + else if (filter.isMember("genre")) + videoUrl.AddOption("genre", filter["genre"].asString()); + else if (filter.isMember("year")) + videoUrl.AddOption("year", (int)filter["year"].asInteger()); + else if (filter.isMember("actor")) + videoUrl.AddOption("actor", filter["actor"].asString()); + else if (filter.isMember("studio")) + videoUrl.AddOption("studio", filter["studio"].asString()); + else if (filter.isMember("tag")) + videoUrl.AddOption("tag", filter["tag"].asString()); + else if (filter.isObject()) + { + std::string xsp; + if (!GetXspFiltering("tvshows", filter, xsp)) + return InvalidParams; + + videoUrl.AddOption("xsp", xsp); + } + + CFileItemList items; + CDatabase::Filter nofilter; + if (!videodatabase.GetTvShowsByWhere(videoUrl.ToString(), nofilter, items, sorting, RequiresAdditionalDetails(MediaTypeTvShow, parameterObject))) + return InvalidParams; + + return HandleItems("tvshowid", "tvshows", items, parameterObject, result, false); +} + +JSONRPC_STATUS CVideoLibrary::GetTVShowDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + int id = (int)parameterObject["tvshowid"].asInteger(); + + CFileItemPtr fileItem(new CFileItem()); + CVideoInfoTag infos; + if (!videodatabase.GetTvShowInfo("", infos, id, fileItem.get(), RequiresAdditionalDetails(MediaTypeTvShow, parameterObject)) || infos.m_iDbId <= 0) + return InvalidParams; + + fileItem->SetFromVideoInfoTag(infos); + HandleFileItem("tvshowid", true, "tvshowdetails", fileItem, parameterObject, parameterObject["properties"], result, false); + return OK; +} + +JSONRPC_STATUS CVideoLibrary::GetSeasons(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + int tvshowID = (int)parameterObject["tvshowid"].asInteger(); + + std::string strPath = StringUtils::Format("videodb://tvshows/titles/{}/", tvshowID); + CFileItemList items; + if (!videodatabase.GetSeasonsNav(strPath, items, -1, -1, -1, -1, tvshowID, false)) + return InternalError; + + HandleFileItemList("seasonid", false, "seasons", items, parameterObject, result); + return OK; +} + +JSONRPC_STATUS CVideoLibrary::GetSeasonDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + int id = (int)parameterObject["seasonid"].asInteger(); + + CVideoInfoTag infos; + if (!videodatabase.GetSeasonInfo(id, infos) || + infos.m_iDbId <= 0 || infos.m_iIdShow <= 0) + return InvalidParams; + + CFileItemPtr pItem = CFileItemPtr(new CFileItem(infos)); + HandleFileItem("seasonid", false, "seasondetails", pItem, parameterObject, parameterObject["properties"], result, false); + return OK; +} + +JSONRPC_STATUS CVideoLibrary::GetEpisodes(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + SortDescription sorting; + ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd); + if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes)) + return InvalidParams; + + int tvshowID = (int)parameterObject["tvshowid"].asInteger(); + int season = (int)parameterObject["season"].asInteger(); + + std::string strPath = StringUtils::Format("videodb://tvshows/titles/{}/{}/", tvshowID, season); + + CVideoDbUrl videoUrl; + if (!videoUrl.FromString(strPath)) + return InternalError; + + const CVariant &filter = parameterObject["filter"]; + if (filter.isMember("genreid")) + videoUrl.AddOption("genreid", (int)filter["genreid"].asInteger()); + else if (filter.isMember("genre")) + videoUrl.AddOption("genre", filter["genre"].asString()); + else if (filter.isMember("year")) + videoUrl.AddOption("year", (int)filter["year"].asInteger()); + else if (filter.isMember("actor")) + videoUrl.AddOption("actor", filter["actor"].asString()); + else if (filter.isMember("director")) + videoUrl.AddOption("director", filter["director"].asString()); + else if (filter.isObject()) + { + std::string xsp; + if (!GetXspFiltering("episodes", filter, xsp)) + return InvalidParams; + + videoUrl.AddOption("xsp", xsp); + } + + if (tvshowID <= 0 && (season > 0 || videoUrl.HasOption("genreid") || videoUrl.HasOption("genre") || videoUrl.HasOption("actor"))) + return InvalidParams; + + if (tvshowID > 0) + { + videoUrl.AddOption("tvshowid", tvshowID); + if (season >= 0) + videoUrl.AddOption("season", season); + } + + CFileItemList items; + if (!videodatabase.GetEpisodesByWhere(videoUrl.ToString(), CDatabase::Filter(), items, false, sorting, RequiresAdditionalDetails(MediaTypeEpisode, parameterObject))) + return InvalidParams; + + return HandleItems("episodeid", "episodes", items, parameterObject, result, false); +} + +JSONRPC_STATUS CVideoLibrary::GetEpisodeDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + int id = (int)parameterObject["episodeid"].asInteger(); + + CVideoInfoTag infos; + if (!videodatabase.GetEpisodeInfo("", infos, id, RequiresAdditionalDetails(MediaTypeEpisode, parameterObject)) || infos.m_iDbId <= 0) + return InvalidParams; + + CFileItemPtr pItem = CFileItemPtr(new CFileItem(infos)); + // We need to set the correct base path to get the valid fanart + int tvshowid = infos.m_iIdShow; + if (tvshowid <= 0) + tvshowid = videodatabase.GetTvShowForEpisode(id); + + std::string basePath = + StringUtils::Format("videodb://tvshows/titles/{}/{}/{}", tvshowid, infos.m_iSeason, id); + pItem->SetPath(basePath); + + HandleFileItem("episodeid", true, "episodedetails", pItem, parameterObject, parameterObject["properties"], result, false); + return OK; +} + +JSONRPC_STATUS CVideoLibrary::GetMusicVideos(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + SortDescription sorting; + ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd); + if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes)) + return InvalidParams; + + CVideoDbUrl videoUrl; + if (!videoUrl.FromString("videodb://musicvideos/titles/")) + return InternalError; + + int genreID = -1, year = -1; + const CVariant &filter = parameterObject["filter"]; + if (filter.isMember("artist")) + videoUrl.AddOption("artist", filter["artist"].asString()); + else if (filter.isMember("genreid")) + genreID = (int)filter["genreid"].asInteger(); + else if (filter.isMember("genre")) + videoUrl.AddOption("genre", filter["genre"].asString()); + else if (filter.isMember("year")) + year = (int)filter["year"].asInteger(); + else if (filter.isMember("director")) + videoUrl.AddOption("director", filter["director"].asString()); + else if (filter.isMember("studio")) + videoUrl.AddOption("studio", filter["studio"].asString()); + else if (filter.isMember("tag")) + videoUrl.AddOption("tag", filter["tag"].asString()); + else if (filter.isObject()) + { + std::string xsp; + if (!GetXspFiltering("musicvideos", filter, xsp)) + return InvalidParams; + + videoUrl.AddOption("xsp", xsp); + } + + CFileItemList items; + if (!videodatabase.GetMusicVideosNav(videoUrl.ToString(), items, genreID, year, -1, -1, -1, -1, -1, sorting, RequiresAdditionalDetails(MediaTypeMusicVideo, parameterObject))) + return InternalError; + + return HandleItems("musicvideoid", "musicvideos", items, parameterObject, result, false); +} + +JSONRPC_STATUS CVideoLibrary::GetMusicVideoDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + int id = (int)parameterObject["musicvideoid"].asInteger(); + + CVideoInfoTag infos; + if (!videodatabase.GetMusicVideoInfo("", infos, id, RequiresAdditionalDetails(MediaTypeMusicVideo, parameterObject)) || infos.m_iDbId <= 0) + return InvalidParams; + + HandleFileItem("musicvideoid", true, "musicvideodetails", CFileItemPtr(new CFileItem(infos)), parameterObject, parameterObject["properties"], result, false); + return OK; +} + +JSONRPC_STATUS CVideoLibrary::GetRecentlyAddedMovies(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CFileItemList items; + if (!videodatabase.GetRecentlyAddedMoviesNav("videodb://recentlyaddedmovies/", items, 0, RequiresAdditionalDetails(MediaTypeMovie, parameterObject))) + return InternalError; + + return HandleItems("movieid", "movies", items, parameterObject, result, true); +} + +JSONRPC_STATUS CVideoLibrary::GetRecentlyAddedEpisodes(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CFileItemList items; + if (!videodatabase.GetRecentlyAddedEpisodesNav("videodb://recentlyaddedepisodes/", items, 0, RequiresAdditionalDetails(MediaTypeEpisode, parameterObject))) + return InternalError; + + return HandleItems("episodeid", "episodes", items, parameterObject, result, true); +} + +JSONRPC_STATUS CVideoLibrary::GetRecentlyAddedMusicVideos(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CFileItemList items; + if (!videodatabase.GetRecentlyAddedMusicVideosNav("videodb://recentlyaddedmusicvideos/", items, 0, RequiresAdditionalDetails(MediaTypeMusicVideo, parameterObject))) + return InternalError; + + return HandleItems("musicvideoid", "musicvideos", items, parameterObject, result, true); +} + +JSONRPC_STATUS CVideoLibrary::GetInProgressTVShows(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CFileItemList items; + if (!videodatabase.GetInProgressTvShowsNav("videodb://inprogresstvshows/", items, 0, RequiresAdditionalDetails(MediaTypeTvShow, parameterObject))) + return InternalError; + + return HandleItems("tvshowid", "tvshows", items, parameterObject, result, false); +} + +JSONRPC_STATUS CVideoLibrary::GetGenres(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string media = parameterObject["type"].asString(); + StringUtils::ToLower(media); + VideoDbContentType idContent = VideoDbContentType::UNKNOWN; + + std::string strPath = "videodb://"; + /* select which video content to get genres from*/ + if (media == MediaTypeMovie) + { + idContent = VideoDbContentType::MOVIES; + strPath += "movies"; + } + else if (media == MediaTypeTvShow) + { + idContent = VideoDbContentType::TVSHOWS; + strPath += "tvshows"; + } + else if (media == MediaTypeMusicVideo) + { + idContent = VideoDbContentType::MUSICVIDEOS; + strPath += "musicvideos"; + } + strPath += "/genres/"; + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CFileItemList items; + if (!videodatabase.GetGenresNav(strPath, items, idContent)) + return InternalError; + + /* need to set strTitle in each item*/ + for (unsigned int i = 0; i < (unsigned int)items.Size(); i++) + items[i]->GetVideoInfoTag()->m_strTitle = items[i]->GetLabel(); + + HandleFileItemList("genreid", false, "genres", items, parameterObject, result); + return OK; +} + +JSONRPC_STATUS CVideoLibrary::GetTags(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string media = parameterObject["type"].asString(); + StringUtils::ToLower(media); + VideoDbContentType idContent = VideoDbContentType::UNKNOWN; + + std::string strPath = "videodb://"; + /* select which video content to get tags from*/ + if (media == MediaTypeMovie) + { + idContent = VideoDbContentType::MOVIES; + strPath += "movies"; + } + else if (media == MediaTypeTvShow) + { + idContent = VideoDbContentType::TVSHOWS; + strPath += "tvshows"; + } + else if (media == MediaTypeMusicVideo) + { + idContent = VideoDbContentType::MUSICVIDEOS; + strPath += "musicvideos"; + } + strPath += "/tags/"; + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CFileItemList items; + if (!videodatabase.GetTagsNav(strPath, items, idContent)) + return InternalError; + + /* need to set strTitle in each item*/ + for (int i = 0; i < items.Size(); i++) + items[i]->GetVideoInfoTag()->m_strTitle = items[i]->GetLabel(); + + HandleFileItemList("tagid", false, "tags", items, parameterObject, result); + return OK; +} + +namespace +{ + const std::map<std::string, std::string> mediaIDTypes = { + {"episodeid", MediaTypeEpisode}, + {"tvshowid", MediaTypeTvShow}, + {"seasonid", MediaTypeSeason}, + {"movieid", MediaTypeMovie}, + {"setid", MediaTypeVideoCollection}, + {"musicvideoid", MediaTypeMusicVideo}, + }; +} + +JSONRPC_STATUS CVideoLibrary::GetAvailableArtTypes(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result) +{ + std::string mediaType; + int mediaID = -1; + for (const auto& mediaIDType : mediaIDTypes) { + if (parameterObject["item"].isMember(mediaIDType.first)) + { + mediaType = mediaIDType.second; + mediaID = parameterObject["item"][mediaIDType.first].asInteger32(); + break; + } + } + if (mediaID == -1) + return InternalError; + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVariant availablearttypes = CVariant(CVariant::VariantTypeArray); + for (const auto& artType : videodatabase.GetAvailableArtTypesForItem(mediaID, mediaType)) + { + availablearttypes.append(artType); + } + result = CVariant(CVariant::VariantTypeObject); + result["availablearttypes"] = availablearttypes; + + return OK; +} + +JSONRPC_STATUS CVideoLibrary::GetAvailableArt(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result) +{ + std::string mediaType; + int mediaID = -1; + for (const auto& mediaIDType : mediaIDTypes) { + if (parameterObject["item"].isMember(mediaIDType.first)) + { + mediaType = mediaIDType.second; + mediaID = parameterObject["item"][mediaIDType.first].asInteger32(); + break; + } + } + if (mediaID == -1) + return InternalError; + + std::string artType = parameterObject["arttype"].asString(); + StringUtils::ToLower(artType); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVariant availableart = CVariant(CVariant::VariantTypeArray); + for (const auto& artentry : videodatabase.GetAvailableArtForItem(mediaID, mediaType, artType)) + { + CVariant item = CVariant(CVariant::VariantTypeObject); + item["url"] = CTextureUtils::GetWrappedImageURL(artentry.m_url); + item["arttype"] = artentry.m_aspect; + if (!artentry.m_preview.empty()) + item["previewurl"] = CTextureUtils::GetWrappedImageURL(artentry.m_preview); + availableart.append(item); + } + result = CVariant(CVariant::VariantTypeObject); + result["availableart"] = availableart; + + return OK; +} + +JSONRPC_STATUS CVideoLibrary::SetMovieDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["movieid"].asInteger(); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + if (!videodatabase.GetMovieInfo("", infos, id) || infos.m_iDbId <= 0) + return InvalidParams; + + // get artwork + std::map<std::string, std::string> artwork; + videodatabase.GetArtForItem(infos.m_iDbId, infos.m_type, artwork); + + int playcount = infos.GetPlayCount(); + CDateTime lastPlayed = infos.m_lastPlayed; + + std::set<std::string> removedArtwork; + std::set<std::string> updatedDetails; + UpdateVideoTag(parameterObject, infos, artwork, removedArtwork, updatedDetails); + + if (videodatabase.UpdateDetailsForMovie(id, infos, artwork, updatedDetails) <= 0) + return InternalError; + + if (!videodatabase.RemoveArtForItem(infos.m_iDbId, MediaTypeMovie, removedArtwork)) + return InternalError; + + if (playcount != infos.GetPlayCount() || lastPlayed != infos.m_lastPlayed) + { + // restore original playcount or the new one won't be announced + int newPlaycount = infos.GetPlayCount(); + infos.SetPlayCount(playcount); + videodatabase.SetPlayCount(CFileItem(infos), newPlaycount, infos.m_lastPlayed); + } + + UpdateResumePoint(parameterObject, infos, videodatabase); + + CJSONRPCUtils::NotifyItemUpdated(infos, artwork); + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::SetMovieSetDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["setid"].asInteger(); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + videodatabase.GetSetInfo(id, infos); + if (infos.m_iDbId <= 0) + { + videodatabase.Close(); + return InvalidParams; + } + + // get artwork + std::map<std::string, std::string> artwork; + videodatabase.GetArtForItem(infos.m_iDbId, infos.m_type, artwork); + + std::set<std::string> removedArtwork; + std::set<std::string> updatedDetails; + UpdateVideoTag(parameterObject, infos, artwork, removedArtwork, updatedDetails); + + if (videodatabase.SetDetailsForMovieSet(infos, artwork, id) <= 0) + return InternalError; + + if (!videodatabase.RemoveArtForItem(infos.m_iDbId, "set", removedArtwork)) + return InternalError; + + CJSONRPCUtils::NotifyItemUpdated(); + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::SetTVShowDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["tvshowid"].asInteger(); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + if (!videodatabase.GetTvShowInfo("", infos, id) || infos.m_iDbId <= 0) + return InvalidParams; + + // get artwork + std::map<std::string, std::string> artwork; + videodatabase.GetArtForItem(infos.m_iDbId, infos.m_type, artwork); + + std::map<int, std::map<std::string, std::string> > seasonArt; + videodatabase.GetTvShowSeasonArt(infos.m_iDbId, seasonArt); + + std::set<std::string> removedArtwork; + std::set<std::string> updatedDetails; + UpdateVideoTag(parameterObject, infos, artwork, removedArtwork, updatedDetails); + + // we need to manually remove tags/taglinks for now because they aren't replaced + // due to scrapers not supporting them + videodatabase.RemoveTagsFromItem(id, MediaTypeTvShow); + + if (!videodatabase.UpdateDetailsForTvShow(id, infos, artwork, seasonArt)) + return InternalError; + + if (!videodatabase.RemoveArtForItem(infos.m_iDbId, MediaTypeTvShow, removedArtwork)) + return InternalError; + + CJSONRPCUtils::NotifyItemUpdated(); + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::SetSeasonDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["seasonid"].asInteger(); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + videodatabase.GetSeasonInfo(id, infos); + if (infos.m_iDbId <= 0 || infos.m_iIdShow <= 0) + { + videodatabase.Close(); + return InvalidParams; + } + + // get artwork + std::map<std::string, std::string> artwork; + videodatabase.GetArtForItem(infos.m_iDbId, infos.m_type, artwork); + + std::set<std::string> removedArtwork; + std::set<std::string> updatedDetails; + UpdateVideoTag(parameterObject, infos, artwork, removedArtwork, updatedDetails); + if (ParameterNotNull(parameterObject, "title")) + infos.SetSortTitle(parameterObject["title"].asString()); + + if (videodatabase.SetDetailsForSeason(infos, artwork, infos.m_iIdShow, id) <= 0) + return InternalError; + + if (!videodatabase.RemoveArtForItem(infos.m_iDbId, MediaTypeSeason, removedArtwork)) + return InternalError; + + CJSONRPCUtils::NotifyItemUpdated(); + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::SetEpisodeDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["episodeid"].asInteger(); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + videodatabase.GetEpisodeInfo("", infos, id); + if (infos.m_iDbId <= 0) + { + videodatabase.Close(); + return InvalidParams; + } + + int tvshowid = videodatabase.GetTvShowForEpisode(id); + if (tvshowid <= 0) + { + videodatabase.Close(); + return InvalidParams; + } + + // get artwork + std::map<std::string, std::string> artwork; + videodatabase.GetArtForItem(infos.m_iDbId, infos.m_type, artwork); + + int playcount = infos.GetPlayCount(); + CDateTime lastPlayed = infos.m_lastPlayed; + + std::set<std::string> removedArtwork; + std::set<std::string> updatedDetails; + UpdateVideoTag(parameterObject, infos, artwork, removedArtwork, updatedDetails); + + if (videodatabase.SetDetailsForEpisode(infos, artwork, tvshowid, id) <= 0) + return InternalError; + + if (!videodatabase.RemoveArtForItem(infos.m_iDbId, MediaTypeEpisode, removedArtwork)) + return InternalError; + + if (playcount != infos.GetPlayCount() || lastPlayed != infos.m_lastPlayed) + { + // restore original playcount or the new one won't be announced + int newPlaycount = infos.GetPlayCount(); + infos.SetPlayCount(playcount); + videodatabase.SetPlayCount(CFileItem(infos), newPlaycount, infos.m_lastPlayed); + } + + UpdateResumePoint(parameterObject, infos, videodatabase); + + CJSONRPCUtils::NotifyItemUpdated(); + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::SetMusicVideoDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["musicvideoid"].asInteger(); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + videodatabase.GetMusicVideoInfo("", infos, id); + if (infos.m_iDbId <= 0) + { + videodatabase.Close(); + return InvalidParams; + } + + // get artwork + std::map<std::string, std::string> artwork; + videodatabase.GetArtForItem(infos.m_iDbId, infos.m_type, artwork); + + int playcount = infos.GetPlayCount(); + CDateTime lastPlayed = infos.m_lastPlayed; + + std::set<std::string> removedArtwork; + std::set<std::string> updatedDetails; + UpdateVideoTag(parameterObject, infos, artwork, removedArtwork, updatedDetails); + + // we need to manually remove tags/taglinks for now because they aren't replaced + // due to scrapers not supporting them + videodatabase.RemoveTagsFromItem(id, MediaTypeMusicVideo); + + if (videodatabase.SetDetailsForMusicVideo(infos, artwork, id) <= 0) + return InternalError; + + if (!videodatabase.RemoveArtForItem(infos.m_iDbId, MediaTypeMusicVideo, removedArtwork)) + return InternalError; + + if (playcount != infos.GetPlayCount()|| lastPlayed != infos.m_lastPlayed) + { + // restore original playcount or the new one won't be announced + int newPlaycount = infos.GetPlayCount(); + infos.SetPlayCount(playcount); + videodatabase.SetPlayCount(CFileItem(infos), newPlaycount, infos.m_lastPlayed); + } + + UpdateResumePoint(parameterObject, infos, videodatabase); + + CJSONRPCUtils::NotifyItemUpdated(); + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::RefreshMovie(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = static_cast<int>(parameterObject["movieid"].asInteger()); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + if (!videodatabase.GetMovieInfo("", infos, id) || infos.m_iDbId <= 0) + return InvalidParams; + + bool ignoreNfo = parameterObject["ignorenfo"].asBoolean(); + std::string searchTitle = parameterObject["title"].asString(); + CVideoLibraryQueue::GetInstance().RefreshItem(CFileItemPtr(new CFileItem(infos)), ignoreNfo, true, false, searchTitle); + + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::RefreshTVShow(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = static_cast<int>(parameterObject["tvshowid"].asInteger()); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CFileItemPtr item(new CFileItem()); + CVideoInfoTag infos; + if (!videodatabase.GetTvShowInfo("", infos, id, item.get()) || infos.m_iDbId <= 0) + return InvalidParams; + + item->SetFromVideoInfoTag(infos); + + bool ignoreNfo = parameterObject["ignorenfo"].asBoolean(); + bool refreshEpisodes = parameterObject["refreshepisodes"].asBoolean(); + std::string searchTitle = parameterObject["title"].asString(); + CVideoLibraryQueue::GetInstance().RefreshItem(item, ignoreNfo, true, refreshEpisodes, searchTitle); + + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::RefreshEpisode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["episodeid"].asInteger(); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + if (!videodatabase.GetEpisodeInfo("", infos, id) || infos.m_iDbId <= 0) + return InvalidParams; + + CFileItemPtr item = CFileItemPtr(new CFileItem(infos)); + // We need to set the correct base path to get the valid fanart + int tvshowid = infos.m_iIdShow; + if (tvshowid <= 0) + tvshowid = videodatabase.GetTvShowForEpisode(id); + + bool ignoreNfo = parameterObject["ignorenfo"].asBoolean(); + std::string searchTitle = parameterObject["title"].asString(); + CVideoLibraryQueue::GetInstance().RefreshItem(item, ignoreNfo, true, false, searchTitle); + + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::RefreshMusicVideo(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = static_cast<int>(parameterObject["musicvideoid"].asInteger()); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + if (!videodatabase.GetMusicVideoInfo("", infos, id) || infos.m_iDbId <= 0) + return InvalidParams; + + bool ignoreNfo = parameterObject["ignorenfo"].asBoolean(); + std::string searchTitle = parameterObject["title"].asString(); + CVideoLibraryQueue::GetInstance().RefreshItem(CFileItemPtr(new CFileItem(infos)), ignoreNfo, true, false, searchTitle); + + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::RemoveMovie(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return RemoveVideo(parameterObject); +} + +JSONRPC_STATUS CVideoLibrary::RemoveTVShow(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return RemoveVideo(parameterObject); +} + +JSONRPC_STATUS CVideoLibrary::RemoveEpisode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return RemoveVideo(parameterObject); +} + +JSONRPC_STATUS CVideoLibrary::RemoveMusicVideo(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return RemoveVideo(parameterObject); +} + +JSONRPC_STATUS CVideoLibrary::Scan(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string directory = parameterObject["directory"].asString(); + std::string cmd = + StringUtils::Format("updatelibrary(video, {}, {})", StringUtils::Paramify(directory), + parameterObject["showdialogs"].asBoolean() ? "true" : "false"); + + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd); + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::Export(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string cmd; + if (parameterObject["options"].isMember("path")) + cmd = StringUtils::Format("exportlibrary2(video, singlefile, {})", + StringUtils::Paramify(parameterObject["options"]["path"].asString())); + else + { + cmd = "exportlibrary2(video, separate, dummy"; + if (parameterObject["options"]["images"].isBoolean() && + parameterObject["options"]["images"].asBoolean() == true) + cmd += ", artwork"; + if (parameterObject["options"]["overwrite"].isBoolean() && + parameterObject["options"]["overwrite"].asBoolean() == true) + cmd += ", overwrite"; + if (parameterObject["options"]["actorthumbs"].isBoolean() && + parameterObject["options"]["actorthumbs"].asBoolean() == true) + cmd += ", actorthumbs"; + cmd += ")"; + } + + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd); + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::Clean(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string directory = parameterObject["directory"].asString(); + std::string cmd; + if (parameterObject["content"].empty()) + cmd = StringUtils::Format("cleanlibrary(video, {0}, {1})", + parameterObject["showdialogs"].asBoolean() ? "true" : "false", + StringUtils::Paramify(directory)); + else + cmd = StringUtils::Format("cleanlibrary({0}, {1}, {2})", parameterObject["content"].asString(), + parameterObject["showdialogs"].asBoolean() ? "true" : "false", + StringUtils::Paramify(directory)); + + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd); + return ACK; +} + +bool CVideoLibrary::FillFileItem( + const std::string& strFilename, + std::shared_ptr<CFileItem>& item, + const CVariant& parameterObject /* = CVariant(CVariant::VariantTypeArray) */) +{ + CVideoDatabase videodatabase; + if (strFilename.empty()) + return false; + + bool filled = false; + if (videodatabase.Open()) + { + CVideoInfoTag details; + if (videodatabase.LoadVideoInfo(strFilename, details)) + { + item->SetFromVideoInfoTag(details); + item->SetDynPath(strFilename); + filled = true; + } + } + + if (item->GetLabel().empty()) + { + item->SetLabel(CUtil::GetTitleFromPath(strFilename, false)); + if (item->GetLabel().empty()) + item->SetLabel(URIUtils::GetFileName(strFilename)); + } + + return filled; +} + +bool CVideoLibrary::FillFileItemList(const CVariant ¶meterObject, CFileItemList &list) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return false; + + std::string file = parameterObject["file"].asString(); + int movieID = (int)parameterObject["movieid"].asInteger(-1); + int episodeID = (int)parameterObject["episodeid"].asInteger(-1); + int musicVideoID = (int)parameterObject["musicvideoid"].asInteger(-1); + int recordingID = static_cast<int>(parameterObject["recordingid"].asInteger()); + + bool success = false; + CFileItemPtr fileItem(new CFileItem()); + if (FillFileItem(file, fileItem)) + { + success = true; + list.Add(fileItem); + } + + if (movieID > 0) + { + CVideoInfoTag details; + videodatabase.GetMovieInfo("", details, movieID); + if (!details.IsEmpty()) + { + list.Add(CFileItemPtr(new CFileItem(details))); + success = true; + } + } + if (episodeID > 0) + { + CVideoInfoTag details; + if (videodatabase.GetEpisodeInfo("", details, episodeID) && !details.IsEmpty()) + { + list.Add(CFileItemPtr(new CFileItem(details))); + success = true; + } + } + if (musicVideoID > 0) + { + CVideoInfoTag details; + videodatabase.GetMusicVideoInfo("", details, musicVideoID); + if (!details.IsEmpty()) + { + list.Add(CFileItemPtr(new CFileItem(details))); + success = true; + } + } + if (recordingID > 0) + { + std::shared_ptr<CFileItem> recordingFileItem = + CPVROperations::GetRecordingFileItem(recordingID); + + if (recordingFileItem) + { + list.Add(recordingFileItem); + success = true; + } + } + + return success; +} + +int CVideoLibrary::RequiresAdditionalDetails(const MediaType& mediaType, const CVariant ¶meterObject) +{ + if (mediaType != MediaTypeMovie && mediaType != MediaTypeTvShow && mediaType != MediaTypeEpisode && mediaType != MediaTypeMusicVideo) + return VideoDbDetailsNone; + + return GetDetailsFromJsonParameters(parameterObject); +} + +int CVideoLibrary::GetDetailsFromJsonParameters(const CVariant& parameterObject) +{ + const CVariant& properties = parameterObject["properties"]; + int details = VideoDbDetailsNone; + for (CVariant::const_iterator_array itr = properties.begin_array(); itr != properties.end_array(); + ++itr) + { + std::string propertyValue = itr->asString(); + if (propertyValue == "cast") + details = details | VideoDbDetailsCast; + else if (propertyValue == "ratings") + details = details | VideoDbDetailsRating; + else if (propertyValue == "uniqueid") + details = details | VideoDbDetailsUniqueID; + else if (propertyValue == "showlink") + details = details | VideoDbDetailsShowLink; + else if (propertyValue == "streamdetails") + details = details | VideoDbDetailsStream; + else if (propertyValue == "tag") + details = details | VideoDbDetailsTag; + } + return details; +} + +JSONRPC_STATUS CVideoLibrary::HandleItems(const char *idProperty, const char *resultName, CFileItemList &items, const CVariant ¶meterObject, CVariant &result, bool limit /* = true */) +{ + int size = items.Size(); + if (!limit && items.HasProperty("total") && items.GetProperty("total").asInteger() > size) + size = (int)items.GetProperty("total").asInteger(); + HandleFileItemList(idProperty, true, resultName, items, parameterObject, result, size, limit); + + return OK; +} + +JSONRPC_STATUS CVideoLibrary::RemoveVideo(const CVariant ¶meterObject) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + if (parameterObject.isMember("movieid")) + videodatabase.DeleteMovie((int)parameterObject["movieid"].asInteger()); + else if (parameterObject.isMember("tvshowid")) + videodatabase.DeleteTvShow((int)parameterObject["tvshowid"].asInteger()); + else if (parameterObject.isMember("episodeid")) + videodatabase.DeleteEpisode((int)parameterObject["episodeid"].asInteger()); + else if (parameterObject.isMember("musicvideoid")) + videodatabase.DeleteMusicVideo((int)parameterObject["musicvideoid"].asInteger()); + + CJSONRPCUtils::NotifyItemUpdated(); + return ACK; +} + +void CVideoLibrary::UpdateResumePoint(const CVariant ¶meterObject, CVideoInfoTag &details, CVideoDatabase &videodatabase) +{ + if (!parameterObject["resume"].isNull()) + { + double position = (double)parameterObject["resume"]["position"].asDouble(); + if (position == 0.0) + videodatabase.ClearBookMarksOfFile(details.m_strFileNameAndPath, CBookmark::RESUME); + else + { + CBookmark bookmark; + double total = (double)parameterObject["resume"]["total"].asDouble(); + if (total <= 0.0 && !videodatabase.GetResumeBookMark(details.m_strFileNameAndPath, bookmark)) + bookmark.totalTimeInSeconds = details.m_streamDetails.GetVideoDuration(); + else + bookmark.totalTimeInSeconds = total; + + bookmark.timeInSeconds = position; + videodatabase.AddBookMarkToFile(details.m_strFileNameAndPath, bookmark, CBookmark::RESUME); + } + } +} + +void CVideoLibrary::UpdateVideoTagField(const CVariant& parameterObject, const std::string& fieldName, std::vector<std::string>& fieldValue, std::set<std::string>& updatedDetails) +{ + if (ParameterNotNull(parameterObject, fieldName)) + { + CopyStringArray(parameterObject[fieldName], fieldValue); + updatedDetails.insert(fieldName); + } +} + +void CVideoLibrary::UpdateVideoTag(const CVariant ¶meterObject, CVideoInfoTag& details, std::map<std::string, std::string> &artwork, std::set<std::string> &removedArtwork, std::set<std::string> &updatedDetails) +{ + if (ParameterNotNull(parameterObject, "title")) + details.SetTitle(parameterObject["title"].asString()); + if (ParameterNotNull(parameterObject, "playcount")) + details.SetPlayCount(static_cast<int>(parameterObject["playcount"].asInteger())); + if (ParameterNotNull(parameterObject, "runtime")) + details.SetDuration(static_cast<int>(parameterObject["runtime"].asInteger())); + + std::vector<std::string> director(details.m_director); + UpdateVideoTagField(parameterObject, "director", director, updatedDetails); + details.SetDirector(director); + + std::vector<std::string> studio(details.m_studio); + UpdateVideoTagField(parameterObject, "studio", studio, updatedDetails); + details.SetStudio(studio); + + if (ParameterNotNull(parameterObject, "plot")) + details.SetPlot(parameterObject["plot"].asString()); + if (ParameterNotNull(parameterObject, "album")) + details.SetAlbum(parameterObject["album"].asString()); + + std::vector<std::string> artist(details.m_artist); + UpdateVideoTagField(parameterObject, "artist", artist, updatedDetails); + details.SetArtist(artist); + + std::vector<std::string> genre(details.m_genre); + UpdateVideoTagField(parameterObject, "genre", genre, updatedDetails); + details.SetGenre(genre); + + if (ParameterNotNull(parameterObject, "track")) + details.m_iTrack = (int)parameterObject["track"].asInteger(); + if (ParameterNotNull(parameterObject, "rating")) + { + details.SetRating(parameterObject["rating"].asFloat()); + updatedDetails.insert("ratings"); + } + if (ParameterNotNull(parameterObject, "votes")) + { + details.SetVotes(StringUtils::ReturnDigits(parameterObject["votes"].asString())); + updatedDetails.insert("ratings"); //Votes and ratings both need updates now, this will trigger those + } + if (ParameterNotNull(parameterObject, "ratings")) + { + CVariant ratings = parameterObject["ratings"]; + for (CVariant::const_iterator_map rIt = ratings.begin_map(); rIt != ratings.end_map(); ++rIt) + { + if (rIt->second.isObject() && ParameterNotNull(rIt->second, "rating")) + { + const auto& rating = rIt->second; + if (ParameterNotNull(rating, "votes")) + { + details.SetRating(rating["rating"].asFloat(), + static_cast<int>(rating["votes"].asInteger()), + rIt->first, + (ParameterNotNull(rating, "default") && rating["default"].asBoolean())); + } + else + details.SetRating(rating["rating"].asFloat(), rIt->first, (ParameterNotNull(rating, "default") && rating["default"].asBoolean())); + + updatedDetails.insert("ratings"); + } + else if (rIt->second.isNull()) + { + details.RemoveRating(rIt->first); + updatedDetails.insert("ratings"); + } + } + } + if (ParameterNotNull(parameterObject, "userrating")) + details.m_iUserRating = static_cast<int>(parameterObject["userrating"].asInteger()); + if (ParameterNotNull(parameterObject, "mpaa")) + details.SetMPAARating(parameterObject["mpaa"].asString()); + if (ParameterNotNull(parameterObject, "imdbnumber")) + { + details.SetUniqueID(parameterObject["imdbnumber"].asString()); + updatedDetails.insert("uniqueid"); + } + if (ParameterNotNull(parameterObject, "uniqueid")) + { + CVariant uniqueids = parameterObject["uniqueid"]; + for (CVariant::const_iterator_map idIt = uniqueids.begin_map(); idIt != uniqueids.end_map(); + ++idIt) + { + if (idIt->second.isString() && !idIt->second.asString().empty()) + { + details.SetUniqueID(idIt->second.asString(), idIt->first); + updatedDetails.insert("uniqueid"); + } + else if (idIt->second.isNull() && idIt->first != details.GetDefaultUniqueID()) + { + details.RemoveUniqueID(idIt->first); + updatedDetails.insert("uniqueid"); + } + } + } + if (ParameterNotNull(parameterObject, "premiered")) + { + CDateTime premiered; + SetFromDBDate(parameterObject["premiered"], premiered); + details.SetPremiered(premiered); + } + else if (ParameterNotNull(parameterObject, "year")) + details.SetYear((int)parameterObject["year"].asInteger()); + if (ParameterNotNull(parameterObject, "lastplayed")) + SetFromDBDateTime(parameterObject["lastplayed"], details.m_lastPlayed); + if (ParameterNotNull(parameterObject, "firstaired")) + SetFromDBDate(parameterObject["firstaired"], details.m_firstAired); + if (ParameterNotNull(parameterObject, "productioncode")) + details.SetProductionCode(parameterObject["productioncode"].asString()); + if (ParameterNotNull(parameterObject, "season")) + details.m_iSeason = (int)parameterObject["season"].asInteger(); + if (ParameterNotNull(parameterObject, "episode")) + details.m_iEpisode = (int)parameterObject["episode"].asInteger(); + if (ParameterNotNull(parameterObject, "originaltitle")) + details.SetOriginalTitle(parameterObject["originaltitle"].asString()); + if (ParameterNotNull(parameterObject, "trailer")) + details.SetTrailer(parameterObject["trailer"].asString()); + if (ParameterNotNull(parameterObject, "tagline")) + details.SetTagLine(parameterObject["tagline"].asString()); + if (ParameterNotNull(parameterObject, "status")) + details.SetStatus(parameterObject["status"].asString()); + if (ParameterNotNull(parameterObject, "plotoutline")) + details.SetPlotOutline(parameterObject["plotoutline"].asString()); + + std::vector<std::string> credits(details.m_writingCredits); + UpdateVideoTagField(parameterObject, "writer", credits, updatedDetails); + details.SetWritingCredits(credits); + + std::vector<std::string> country(details.m_country); + UpdateVideoTagField(parameterObject, "country", country, updatedDetails); + details.SetCountry(country); + + if (ParameterNotNull(parameterObject, "top250")) + details.m_iTop250 = (int)parameterObject["top250"].asInteger(); + if (ParameterNotNull(parameterObject, "sorttitle")) + details.SetSortTitle(parameterObject["sorttitle"].asString()); + if (ParameterNotNull(parameterObject, "episodeguide")) + details.SetEpisodeGuide(parameterObject["episodeguide"].asString()); + if (ParameterNotNull(parameterObject, "set")) + { + details.SetSet(parameterObject["set"].asString()); + updatedDetails.insert("set"); + } + + std::vector<std::string> showLink(details.m_showLink); + UpdateVideoTagField(parameterObject, "showlink", showLink, updatedDetails); + details.SetShowLink(showLink); + + std::vector<std::string> tags(details.m_tags); + UpdateVideoTagField(parameterObject, "tag", tags, updatedDetails); + details.SetTags(tags); + + if (ParameterNotNull(parameterObject, "thumbnail")) + { + std::string value = parameterObject["thumbnail"].asString(); + artwork["thumb"] = StringUtils::Trim(value); + updatedDetails.insert("art.altered"); + } + if (ParameterNotNull(parameterObject, "fanart")) + { + std::string value = parameterObject["fanart"].asString(); + artwork["fanart"] = StringUtils::Trim(value); + updatedDetails.insert("art.altered"); + } + + if (ParameterNotNull(parameterObject, "art")) + { + CVariant art = parameterObject["art"]; + for (CVariant::const_iterator_map artIt = art.begin_map(); artIt != art.end_map(); ++artIt) + { + if (artIt->second.isString() && !artIt->second.asString().empty()) + { + artwork[artIt->first] = CTextureUtils::UnwrapImageURL(artIt->second.asString()); + updatedDetails.insert("art.altered"); + } + else if (artIt->second.isNull()) + { + artwork.erase(artIt->first); + removedArtwork.insert(artIt->first); + } + } + } + + if (ParameterNotNull(parameterObject, "dateadded")) + { + SetFromDBDateTime(parameterObject["dateadded"], details.m_dateAdded); + updatedDetails.insert("dateadded"); + } +} diff --git a/xbmc/interfaces/json-rpc/VideoLibrary.h b/xbmc/interfaces/json-rpc/VideoLibrary.h new file mode 100644 index 0000000..f91c4c8 --- /dev/null +++ b/xbmc/interfaces/json-rpc/VideoLibrary.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "FileItemHandler.h" +#include "JSONRPC.h" +#include "utils/DatabaseUtils.h" + +#include <memory> +#include <string> +#include <vector> + +class CFileItem; +class CFileItemList; +class CVideoDatabase; +class CVariant; + +namespace JSONRPC +{ + class CVideoLibrary : public CFileItemHandler + { + public: + static JSONRPC_STATUS GetMovies(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetMovieDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetMovieSets(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetMovieSetDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS GetTVShows(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetTVShowDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetSeasons(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetSeasonDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetEpisodes(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetEpisodeDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS GetMusicVideos(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetMusicVideoDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS GetRecentlyAddedMovies(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetRecentlyAddedEpisodes(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetRecentlyAddedMusicVideos(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetInProgressTVShows(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS GetGenres(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetTags(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetAvailableArtTypes(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result); + static JSONRPC_STATUS GetAvailableArt(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result); + + static JSONRPC_STATUS SetMovieDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetMovieSetDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetTVShowDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetSeasonDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetEpisodeDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetMusicVideoDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS RefreshMovie(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS RefreshTVShow(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS RefreshEpisode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS RefreshMusicVideo(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS RemoveMovie(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS RemoveTVShow(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS RemoveEpisode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS RemoveMusicVideo(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS Scan(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Export(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Clean(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static bool FillFileItem( + const std::string& strFilename, + std::shared_ptr<CFileItem>& item, + const CVariant& parameterObject = CVariant(CVariant::VariantTypeArray)); + static bool FillFileItemList(const CVariant ¶meterObject, CFileItemList &list); + static void UpdateResumePoint(const CVariant ¶meterObject, CVideoInfoTag &details, CVideoDatabase &videodatabase); + + /*! \brief Provided the JSON-RPC parameter object compute the VideoDbDetails mask + * \param parameterObject the JSON parameter mask + * \return the mask value for the requested properties + */ + static int GetDetailsFromJsonParameters(const CVariant& parameterObject); + + private: + static int RequiresAdditionalDetails(const MediaType& mediaType, const CVariant ¶meterObject); + static JSONRPC_STATUS HandleItems(const char *idProperty, const char *resultName, CFileItemList &items, const CVariant ¶meterObject, CVariant &result, bool limit = true); + static JSONRPC_STATUS RemoveVideo(const CVariant ¶meterObject); + static void UpdateVideoTag(const CVariant ¶meterObject, CVideoInfoTag &details, std::map<std::string, std::string> &artwork, std::set<std::string> &removedArtwork, std::set<std::string>& updatedDetails); + static void UpdateVideoTagField(const CVariant& parameterObject, const std::string& fieldName, std::vector<std::string>& fieldValue, std::set<std::string>& updatedDetails); + }; +} diff --git a/xbmc/interfaces/json-rpc/XBMCOperations.cpp b/xbmc/interfaces/json-rpc/XBMCOperations.cpp new file mode 100644 index 0000000..67edc29 --- /dev/null +++ b/xbmc/interfaces/json-rpc/XBMCOperations.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "XBMCOperations.h" + +#include "ServiceBroker.h" +#include "messaging/ApplicationMessenger.h" +#include "powermanagement/PowerManager.h" +#include "utils/Variant.h" + +using namespace JSONRPC; + +JSONRPC_STATUS CXBMCOperations::GetInfoLabels(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::vector<std::string> info; + + for (unsigned int i = 0; i < parameterObject["labels"].size(); i++) + { + std::string field = parameterObject["labels"][i].asString(); + StringUtils::ToLower(field); + + info.push_back(parameterObject["labels"][i].asString()); + } + + if (!info.empty()) + { + std::vector<std::string> infoLabels; + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_INFOLABEL, -1, -1, + static_cast<void*>(&infoLabels), "", info); + + for (unsigned int i = 0; i < info.size(); i++) + { + if (i >= infoLabels.size()) + break; + result[info[i]] = infoLabels[i]; + } + } + + return OK; +} + +JSONRPC_STATUS CXBMCOperations::GetInfoBooleans(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::vector<std::string> info; + + bool CanControlPower = (client->GetPermissionFlags() & ControlPower) > 0; + + for (unsigned int i = 0; i < parameterObject["booleans"].size(); i++) + { + std::string field = parameterObject["booleans"][i].asString(); + StringUtils::ToLower(field); + + // Need to override power management of whats in infomanager since jsonrpc + // have a security layer aswell. + if (field == "system.canshutdown") + result[parameterObject["booleans"][i].asString()] = (CServiceBroker::GetPowerManager().CanPowerdown() && CanControlPower); + else if (field == "system.canpowerdown") + result[parameterObject["booleans"][i].asString()] = (CServiceBroker::GetPowerManager().CanPowerdown() && CanControlPower); + else if (field == "system.cansuspend") + result[parameterObject["booleans"][i].asString()] = (CServiceBroker::GetPowerManager().CanSuspend() && CanControlPower); + else if (field == "system.canhibernate") + result[parameterObject["booleans"][i].asString()] = (CServiceBroker::GetPowerManager().CanHibernate() && CanControlPower); + else if (field == "system.canreboot") + result[parameterObject["booleans"][i].asString()] = (CServiceBroker::GetPowerManager().CanReboot() && CanControlPower); + else + info.push_back(parameterObject["booleans"][i].asString()); + } + + if (!info.empty()) + { + std::vector<bool> infoLabels; + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_INFOBOOL, -1, -1, + static_cast<void*>(&infoLabels), "", info); + for (unsigned int i = 0; i < info.size(); i++) + { + if (i >= infoLabels.size()) + break; + result[info[i].c_str()] = CVariant(infoLabels[i]); + } + } + + return OK; +} diff --git a/xbmc/interfaces/json-rpc/XBMCOperations.h b/xbmc/interfaces/json-rpc/XBMCOperations.h new file mode 100644 index 0000000..af56172 --- /dev/null +++ b/xbmc/interfaces/json-rpc/XBMCOperations.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPC.h" + +class CVariant; + +namespace JSONRPC +{ + class CXBMCOperations + { + public: + static JSONRPC_STATUS GetInfoLabels(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetInfoBooleans(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + }; +} diff --git a/xbmc/interfaces/json-rpc/schema/CMakeLists.txt b/xbmc/interfaces/json-rpc/schema/CMakeLists.txt new file mode 100644 index 0000000..a4d5583 --- /dev/null +++ b/xbmc/interfaces/json-rpc/schema/CMakeLists.txt @@ -0,0 +1,30 @@ +set(JSON_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/version.txt + ${CMAKE_CURRENT_SOURCE_DIR}/license.txt + ${CMAKE_CURRENT_SOURCE_DIR}/methods.json + ${CMAKE_CURRENT_SOURCE_DIR}/types.json + ${CMAKE_CURRENT_SOURCE_DIR}/notifications.json) + +add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ServiceDescription.h + COMMAND JsonSchemaBuilder::JsonSchemaBuilder ${JSON_SRCS} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR} + DEPENDS ${JSON_SRCS} + COMMENT "Generating ServiceDescription.h") + +add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/addons/xbmc.json/addon.xml + COMMAND ${CMAKE_COMMAND} + -DCMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR} + -DCORE_BINARY_DIR=${CMAKE_BINARY_DIR} + -DCORE_SYSTEM_NAME=${CORE_SYSTEM_NAME} + -P ${CMAKE_CURRENT_SOURCE_DIR}/GenerateAddonXml.cmake + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${JSON_SRCS} ${CMAKE_SOURCE_DIR}/addons/xbmc.json/addon.xml.in + COMMENT "Generating xbmc.json/addon.xml") + +add_custom_target(generate_json_header ALL + DEPENDS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ServiceDescription.h + ${CMAKE_BINARY_DIR}/addons/xbmc.json/addon.xml) +set_target_properties(generate_json_header PROPERTIES FOLDER "Build Utilities") + +if(BOOTSTRAP_IN_TREE) + add_dependencies(generate_json_header JsonSchemaBuilder::JsonSchemaBuilder) +endif() diff --git a/xbmc/interfaces/json-rpc/schema/GenerateAddonXml.cmake b/xbmc/interfaces/json-rpc/schema/GenerateAddonXml.cmake new file mode 100644 index 0000000..7f0817b --- /dev/null +++ b/xbmc/interfaces/json-rpc/schema/GenerateAddonXml.cmake @@ -0,0 +1,6 @@ +include(${CMAKE_SOURCE_DIR}/cmake/scripts/common/Macros.cmake) +core_find_versions() + +file(REMOVE ${CORE_BINARY_DIR}/addons/xbmc.json/addon.xml) +configure_file(${CMAKE_SOURCE_DIR}/addons/xbmc.json/addon.xml.in + ${CORE_BINARY_DIR}/addons/xbmc.json/addon.xml @ONLY) diff --git a/xbmc/interfaces/json-rpc/schema/license.txt b/xbmc/interfaces/json-rpc/schema/license.txt new file mode 100644 index 0000000..97af310 --- /dev/null +++ b/xbmc/interfaces/json-rpc/schema/license.txt @@ -0,0 +1,7 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ diff --git a/xbmc/interfaces/json-rpc/schema/methods.json b/xbmc/interfaces/json-rpc/schema/methods.json new file mode 100644 index 0000000..4d396cf --- /dev/null +++ b/xbmc/interfaces/json-rpc/schema/methods.json @@ -0,0 +1,2941 @@ +{ + "JSONRPC.Introspect": { + "type": "method", + "description": "Enumerates all actions and descriptions", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "getdescriptions", "type": "boolean", "default": true }, + { "name": "getmetadata", "type": "boolean", "default": false }, + { "name": "filterbytransport", "type": "boolean", "default": true }, + { "name": "filter", "type": "object", + "properties": { + "id": { "type": "string", "required": true, "description": "Name of a namespace, method or type" }, + "type": { "type": "string", "required": true, "enum": [ "method", "namespace", "type", "notification" ], "description": "Type of the given name" }, + "getreferences": { "type": "boolean", "default": true, "description": "Whether or not to print the schema for referenced types" } + } + } + ], + "returns": "object" + }, + "JSONRPC.Version": { + "type": "method", + "description": "Retrieve the JSON-RPC protocol version.", + "transport": "Response", + "permission": "ReadData", + "params": [], + "returns": { + "type": "object", + "properties": { + "version": { "type": "object", "required": true, + "properties": { + "major": { "type": "integer", "minimum": 0, "required": true, "description": "Bumped on backwards incompatible changes to the API definition" }, + "minor": { "type": "integer", "minimum": 0, "required": true, "description": "Bumped on backwards compatible additions/changes to the API definition" }, + "patch": { "type": "integer", "minimum": 0, "required": true, "description": "Bumped on any changes to the internal implementation but not to the API definition" } + } + } + } + } + }, + "JSONRPC.Permission": { + "type": "method", + "description": "Retrieve the clients permissions", + "transport": "Response", + "permission": "ReadData", + "params": [], + "returns": { + "type": "object", + "properties": { + "ReadData": { "type": "boolean", "required": true }, + "ControlPlayback": { "type": "boolean", "required": true }, + "ControlNotify": { "type": "boolean", "required": true }, + "ControlPower": { "type": "boolean", "required": true }, + "UpdateData": { "type": "boolean", "required": true }, + "RemoveData": { "type": "boolean", "required": true }, + "Navigate": { "type": "boolean", "required": true }, + "WriteFile": { "type": "boolean", "required": true }, + "ControlSystem": { "type": "boolean", "required": true }, + "ControlGUI": { "type": "boolean", "required": true }, + "ManageAddon": { "type": "boolean", "required": true }, + "ExecuteAddon": { "type": "boolean", "required": true }, + "ControlPVR": { "type": "boolean", "required": true } + } + } + }, + "JSONRPC.Ping": { + "type": "method", + "description": "Ping responder", + "transport": "Response", + "permission": "ReadData", + "params": [], + "returns": "string" + }, + "JSONRPC.GetConfiguration": { + "type": "method", + "description": "Get client-specific configurations", + "transport": "Announcing", + "permission": "ReadData", + "params": [], + "returns": { "$ref": "Configuration" } + }, + "JSONRPC.SetConfiguration": { + "type": "method", + "description": "Change the client-specific configuration", + "transport": "Announcing", + "permission": "ControlNotify", + "params": [ + { "name": "notifications", "type": "object", + "properties": { + "Player": { "$ref": "Optional.Boolean" }, + "Playlist": { "$ref": "Optional.Boolean" }, + "GUI": { "$ref": "Optional.Boolean" }, + "System": { "$ref": "Optional.Boolean" }, + "AudioLibrary": { "$ref": "Optional.Boolean" }, + "VideoLibrary": { "$ref": "Optional.Boolean" }, + "Application": { "$ref": "Optional.Boolean" }, + "Input": { "$ref": "Optional.Boolean" }, + "Other": { "$ref": "Optional.Boolean" } + } + } + ], + "returns": { "$ref": "Configuration" } + }, + "JSONRPC.NotifyAll": { + "type": "method", + "description": "Notify all other connected clients", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "message", "type": "string", "required": true }, + { "name": "data", "type": "any", "default": null } + ], + "returns": "any" + }, + "Player.Open": { + "type": "method", + "description": "Start playback of either the playlist with the given ID, a slideshow with the pictures from the given directory or a single file or an item from the database.", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "item", + "type": [ + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "playlistid": { "$ref": "Playlist.Id", "required": true }, + "position": { "$ref": "Playlist.Position", "default": 0 } + } + }, + { "$ref": "Playlist.Item", "required": true }, + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "path": { "type": "string", "required": true }, + "random": { "type": "boolean", "default": true, "description": "Deprecated, use the shuffled property of the options parameter instead" }, + "recursive": { "type": "boolean", "default": true } + } + }, + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "partymode": { "type": [ + { "type": "string", "required": true, "enum": [ "music", "video" ] }, + { "type": "string", "required": true, "minLength": 5, "description": "Path to a smartplaylist (*.xsp) file" } + ] + } + } + }, + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "broadcastid": { "$ref": "Library.Id", "required": true } + } + }, + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "channelid": { "$ref": "Library.Id", "required": true } + } + }, + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "recordingid": { "$ref": "Library.Id", "required": true } + } + } + ] + }, + { "name": "options", "type": "object", "additionalProperties": false, + "properties": { + "playername": { "type": [ + "null", + { "type": "string", "enum": [ "default" ], "required": true }, + { "type": "string", "minLength": 1, "required": true, "description": "name of player" } + ], + "default": null + }, + "shuffled": { "$ref": "Optional.Boolean" }, + "repeat": { "type": [ "null", { "$ref": "Player.Repeat", "required": true } ], "default": null }, + "resume": { "type": [ + { "type": "boolean", "required": true, "description": "Whether to resume from the resume point or not" }, + { "$ref": "Player.Position.Percentage", "required": true, "description": "Percentage value to start from" }, + { "$ref": "Player.Position.Time", "required": true, "description": "Time to start from" } + ], + "default": false + } + } + } + ], + "returns": "string" + }, + "Player.GetActivePlayers": { + "type": "method", + "description": "Returns all active players", + "transport": "Response", + "permission": "ReadData", + "params": [], + "returns": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "object", + "properties": { + "playerid": { "$ref": "Player.Id", "required": true }, + "type": { "$ref": "Player.Type", "required": true }, + "playertype": { "type": "string", "enum": [ "internal", "external", "remote" ], "required": true } + } + } + } + }, + "Player.GetPlayers": { + "type": "method", + "description": "Get a list of available players", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "media", "type": "string", "enum": [ "all", "video", "audio" ], "default": "all" } + ], + "returns": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "object", + "properties": { + "name": { "$ref": "Global.String.NotEmpty", "required": true }, + "type": { "type": "string", "enum": [ "internal", "external", "remote" ], "required": true }, + "playsvideo": { "type": "boolean", "required": true }, + "playsaudio": { "type": "boolean", "required": true } + } + } + } + }, + "Player.GetProperties": { + "type": "method", + "description": "Retrieves the values of the given properties", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "properties", "type": "array", "uniqueItems": true, "required": true, "items": { "$ref": "Player.Property.Name" } } + ], + "returns": { "$ref": "Player.Property.Value", "required": true } + }, + "Player.GetItem": { + "type": "method", + "description": "Retrieves the currently played item", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "properties", "$ref": "List.Fields.All" } + ], + "returns": { "type": "object", + "properties": { + "item": { "$ref": "List.Item.All", "required": true } + } + } + }, + "Player.PlayPause": { + "type": "method", + "description": "Pauses or unpause playback and returns the new state", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "play", "$ref": "Global.Toggle", "default": "toggle" } + ], + "returns": { "$ref": "Player.Speed" } + }, + "Player.Stop": { + "type": "method", + "description": "Stops playback", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true } + ], + "returns": "string" + }, + "Player.GetAudioDelay": { + "type": "method", + "description": "Get the audio delay for the current playback", + "transport": "Response", + "permission": "ReadData", + "params": [], + "returns": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "The offset value used in the current playback.", + "required": true + } + } + } + }, + "Player.SetAudioDelay": { + "type": "method", + "description": "Set the audio delay for the current playback", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { + "name": "playerid", + "$ref": "Player.Id", + "required": true + }, + { + "name": "offset", + "type": [ + { + "type": "number", + "description": "The value should be a multiple of 0.025 in a range of +/-10 (the default range can be overriden by advancedsettings.xml).", + "required": true + }, + { + "$ref": "Global.IncrementDecrement", + "required": true + } + ], + "required": true + } + ], + "returns": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "The offset value used in the current playback.", + "required": true + } + } + } + }, + "Player.SetSpeed": { + "type": "method", + "description": "Set the speed of the current playback", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "speed", "type": [ + { "type": "integer", "required": true, "enum": [ -32, -16, -8, -4, -2, -1, 0, 1, 2, 4, 8, 16, 32 ] }, + { "$ref": "Global.IncrementDecrement", "required": true } + ], + "required": true + } + ], + "returns": { "$ref": "Player.Speed" } + }, + "Player.Seek": { + "type": "method", + "description": "Seek through the playing item", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "value", "required": true, "type": [ + { "type": "object", "properties": { "percentage": { "$ref": "Player.Position.Percentage", "required": true, "description": "Percentage value to seek to" } }, "additionalProperties": false, "required": true }, + { "type": "object", "properties": { "time": { "$ref": "Player.Position.Time", "required": true, "description": "Time to seek to" } }, "additionalProperties": false, "required": true }, + { "type": "object", "properties": { "step": { "type": "string", "enum": [ "smallforward", "smallbackward", "bigforward", "bigbackward" ], "required": true, "description": "Seek by predefined jumps" } }, "additionalProperties": false, "required": true }, + { "type": "object", "properties": { "seconds": { "type": "integer", "required": true, "description": "Seek by the given number of seconds" } }, "additionalProperties": false, "required": true } + ] + } + ], + "returns": { + "type": "object", + "properties": { + "percentage": { "$ref": "Player.Position.Percentage" }, + "time": { "$ref": "Global.Time" }, + "totaltime": { "$ref": "Global.Time" } + } + } + }, + "Player.Move": { + "type": "method", + "description": "If picture is zoomed move viewport left/right/up/down otherwise skip previous/next", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "direction", "type": "string", "enum": [ "left", "right", "up", "down" ], "required": true } + ], + "returns": "string" + }, + "Player.Zoom": { + "type": "method", + "description": "Zoom current picture", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "zoom", "type": [ + { "type": "string", "enum": [ "in", "out" ], "required": true }, + { "type": "integer", "minimum": 1, "maximum": 10, "description": "zoom level", "required": true } + ], + "required": true } + ], + "returns": "string" + }, + "Player.SetViewMode": { + "type": "method", + "description": "Set view mode of video player", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "viewmode", "type": [ + { "$ref": "Player.CustomViewMode", "description": "Custom view mode", "required": true }, + { "name": "value", "$ref": "Player.ViewMode", "required": true} + ], + "required": true } + ], + "returns": "string" + }, + "Player.GetViewMode": { + "type": "method", + "description": "Get view mode of video player", + "transport": "Response", + "permission": "ReadData", + "params": [], + "returns": { + "type": "object", + "properties": { + "viewmode": { "$ref": "Player.ViewMode", "required": true }, + "zoom": { "type": "number", "required": true }, + "pixelratio": { "type": "number", "required": true }, + "verticalshift": { "type": "number", "required": true }, + "nonlinearstretch": { "type": "boolean", "required": true } + } + } + }, + "Player.Rotate": { + "type": "method", + "description": "Rotates current picture", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "value", "type": "string", "enum": [ "clockwise", "counterclockwise" ], "default": "clockwise" } + ], + "returns": "string" + }, + "Player.GoTo": { + "type": "method", + "description": "Go to previous/next/specific item in the playlist", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "to", "type": [ + { "type": "string", "enum": [ "previous", "next" ], "required": true }, + { "$ref": "Playlist.Position", "description": "position in playlist", "required": true } + ], + "required": true } + ], + "returns": "string" + }, + "Player.SetShuffle": { + "type": "method", + "description": "Shuffle/Unshuffle items in the player", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "shuffle", "$ref": "Global.Toggle", "required": true } + ], + "returns": "string" + }, + "Player.SetRepeat": { + "type": "method", + "description": "Set the repeat mode of the player", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "repeat", "type": [ + { "$ref": "Player.Repeat", "required": true }, + { "type": "string", "enum": [ "cycle" ], "required": true } + ], + "required": true + } + ], + "returns": "string" + }, + "Player.SetPartymode": { + "type": "method", + "description": "Turn partymode on or off", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "partymode", "$ref": "Global.Toggle", "required": true } + ], + "returns": "string" + }, + "Player.SetAudioStream": { + "type": "method", + "description": "Set the audio stream played by the player", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "stream", "required": true, "type": [ + { "type": "string", "enum": [ "previous", "next" ] }, + { "type": "integer", "minimum": 0, "description": "Index of the audio stream to play" } + ] + } + ], + "returns": "string" + }, + "Player.SetVideoStream": { + "type": "method", + "description": "Set the video stream played by the player", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "stream", "required": true, "type": [ + { "type": "string", "enum": [ "previous", "next" ] }, + { "type": "integer", "minimum": 0, "description": "Index of the video stream to play" } + ] + } + ], + "returns": "string" + }, + "Player.AddSubtitle": { + "type": "method", + "description": "Add subtitle to the player", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "subtitle", "type": "string", "required": true, "description": "Local path or remote URL to the subtitle file to load" } + ], + "returns": "string" + }, + "Player.SetSubtitle": { + "type": "method", + "description": "Set the subtitle displayed by the player", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "subtitle", "required": true, "type": [ + { "type": "string", "enum": [ "previous", "next", "off", "on" ] }, + { "type": "integer", "minimum": 0, "description": "Index of the subtitle to display" } + ] + }, + { "name": "enable", "type": "boolean", "default": false, "description": "Whether to enable subtitles to be displayed after setting the new subtitle" } + ], + "returns": "string" + }, + "Playlist.GetPlaylists": { + "type": "method", + "description": "Returns all existing playlists", + "transport": "Response", + "permission": "ReadData", + "params": [], + "returns": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "object", + "properties": { + "playlistid": { "$ref": "Playlist.Id", "required": true }, + "type": { "$ref": "Playlist.Type", "required": true } + } + } + } + }, + "Playlist.GetProperties": { + "type": "method", + "description": "Retrieves the values of the given properties", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "playlistid", "$ref": "Playlist.Id", "required": true }, + { "name": "properties", "type": "array", "uniqueItems": true, "required": true, "items": { "$ref": "Playlist.Property.Name" } } + ], + "returns": { "$ref": "Playlist.Property.Value", "required": true } + }, + "Playlist.GetItems": { + "type": "method", + "description": "Get all items from playlist", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "playlistid", "$ref": "Playlist.Id", "required": true }, + { "name": "properties", "$ref": "List.Fields.All" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "items": { "type": "array", "items": { "$ref": "List.Item.All" }, "required": true } + } + } + }, + "Playlist.Add": { + "type": "method", + "description": "Add item(s) to playlist", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playlistid", "$ref": "Playlist.Id", "required": true }, + { "name": "item", + "type": [ + { "$ref": "Playlist.Item", "required": true }, + { "type": "array", "items": { "$ref": "Playlist.Item" }, "required": true } + ], + "required": true } + ], + "returns": "string" + }, + "Playlist.Insert": { + "type": "method", + "description": "Insert item(s) into playlist. Does not work for picture playlists (aka slideshows).", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playlistid", "$ref": "Playlist.Id", "required": true }, + { "name": "position", "$ref": "Playlist.Position", "required": true }, + { "name": "item", + "type": [ + { "$ref": "Playlist.Item", "required": true }, + { "type": "array", "items": { "$ref": "Playlist.Item" }, "required": true } + ], + "required": true } + ], + "returns": "string" + }, + "Playlist.Remove": { + "type": "method", + "description": "Remove item from playlist. Does not work for picture playlists (aka slideshows).", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playlistid", "$ref": "Playlist.Id", "required": true }, + { "name": "position", "$ref": "Playlist.Position", "required": true } + ], + "returns": "string" + }, + "Playlist.Clear": { + "type": "method", + "description": "Clear playlist", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playlistid", "$ref": "Playlist.Id", "required": true } + ], + "returns": "string" + }, + "Playlist.Swap": { + "type": "method", + "description": "Swap items in the playlist. Does not work for picture playlists (aka slideshows).", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playlistid", "$ref": "Playlist.Id", "required": true }, + { "name": "position1", "$ref": "Playlist.Position", "required": true }, + { "name": "position2", "$ref": "Playlist.Position", "required": true } + ], + "returns": "string" + }, + "Files.GetSources": { + "type": "method", + "description": "Get the sources of the media windows", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "media", "$ref": "Files.Media", "required": true }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "sources": { "$ref": "List.Items.Sources", "required": true } + } + } + }, + "Files.PrepareDownload": { + "type": "method", + "description": "Provides a way to download a given file (e.g. providing an URL to the real file location)", + "transport": [ "Response", "FileDownloadRedirect" ], + "permission": "ReadData", + "params": [ + { "name": "path", "type": "string", "required": true } + ], + "returns": { + "type": "object", + "properties": { + "protocol": { "type": "string", "enum": [ "http" ], "required": true }, + "details": { "type": "any", "required": true, "description": "Transport specific details on how/from where to download the given file" }, + "mode": { "type": "string", "enum": [ "redirect", "direct" ], "required": true, "description": "Direct mode allows using Files.Download whereas redirect mode requires the usage of a different protocol" } + } + } + }, + "Files.Download": { + "type": "method", + "description": "Downloads the given file", + "transport": [ "Response", "FileDownloadDirect" ], + "permission": "ReadData", + "params": [ + { "name": "path", "type": "string", "required": true } + ], + "returns": { "type": "any", "required": true } + }, + "Files.GetDirectory": { + "type": "method", + "description": "Get the directories and files in the given directory", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "directory", "type": "string", "required": true }, + { "name": "media", "$ref": "Files.Media", "default": "files" }, + { "name": "properties", "$ref": "List.Fields.Files" }, + { "name": "sort", "$ref": "List.Sort" }, + { "name": "limits", "$ref": "List.Limits", "description": "Limits are applied after getting the directory content thus retrieval is not faster when they are applied." } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "files": { "type": "array", "items": { "$ref": "List.Item.File" }, "required": true } + } + } + }, + "Files.GetFileDetails": { + "type": "method", + "description": "Get details for a specific file", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "file", "type": "string", "required": true, "description": "Full path to the file" }, + { "name": "media", "$ref": "Files.Media", "default": "files" }, + { "name": "properties", "$ref": "List.Fields.Files" } + ], + "returns": { + "type": "object", + "properties": { + "filedetails": { "$ref": "List.Item.File", "required": true } + } + } + }, + "Files.SetFileDetails": { + "type": "method", + "description": "Update the given specific file with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "file", "type": "string", "required": true, "description": "Full path to the file" }, + { "name": "media", "$ref": "Files.Media", "required": true, "description": "File type to update correct database. Currently only \"video\" is supported." }, + { "name": "playcount", "$ref": "Optional.Integer" }, + { "name": "lastplayed", "$ref": "Optional.String", "description": "Setting a valid lastplayed without a playcount will force playcount to 1." }, + { "name": "resume", "type": [ "null", { "$ref": "Video.Resume", "required": true } ], "default": null } + ], + "returns": "string" + }, + "AudioLibrary.GetProperties": { + "type": "method", + "description": "Retrieves the values of the music library properties", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "type": "array", "uniqueItems": true, "required": true, "items": { "$ref": "Audio.Property.Name" } } + ], + "returns": { "$ref": "Audio.Property.Value", "required": true } + }, + "AudioLibrary.GetArtists": { + "type": "method", + "description": "Retrieve all artists. For backward compatibility by default this implicitly does not include those that only contribute other roles, however absolutely all artists can be returned using allroles=true", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "albumartistsonly", "$ref": "Optional.Boolean", "description": "Whether or not to only include album artists rather than the artists of only individual songs as well. If the parameter is not passed or is passed as null the GUI setting will be used" }, + { "name": "properties", "$ref": "Audio.Fields.Artist" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" }, + { "name": "filter", + "type": [ + { "type": "object", "properties": { "genreid": { "$ref": "Library.Id", "required": true, "description": "Deprecated, use songgenreid. Filter for existence of songs with this genre" } }, "additionalProperties": false }, + { "type": "object", "properties": { "songgenreid": { "$ref": "Library.Id", "required": true, "description": "Song genreid. Filter for existence of songs with this genre" } }, "additionalProperties": false }, + { "type": "object", "properties": { "songgenreid": { "$ref": "Library.Id", "required": true }, + "roleid": { "$ref": "Library.Id", "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "songgenreid": { "$ref": "Library.Id", "required": true }, + "role": { "type": "string", "minLength": 1, "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "genre": { "type": "string", "minLength": 1, "required": true, "description": "Deprecated, use songgenre. Filter for existence of songs with this genre" } }, "additionalProperties": false }, + { "type": "object", "properties": { "songgenre": { "type": "string", "minLength": 1, "required": true, "description": "Song genre. Filter for existence of songs with this genre" } }, "additionalProperties": false }, + { "type": "object", "properties": { "songgenre": { "type": "string", "minLength": 1, "required": true }, + "roleid": { "$ref": "Library.Id", "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "songgenre": { "type": "string", "minLength": 1, "required": true }, + "role": { "type": "string", "minLength": 1, "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "albumid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "album": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "songid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "songid": { "$ref": "Library.Id", "required": true }, + "roleid": { "$ref": "Library.Id", "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "songid": { "$ref": "Library.Id", "required": true }, + "role": { "type": "string", "minLength": 1, "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "roleid": { "$ref": "Library.Id", "required": true, "description": "Role contributed by artist. Overridden by allroles parameter" } }, "additionalProperties": false }, + { "type": "object", "properties": { "role": { "type": "string", "minLength": 1, "required": true, "description": "Role contributed by artist. Overridden by allroles parameter" } }, "additionalProperties": false }, + { "$ref": "List.Filter.Artists" } + ] + }, + { "name": "allroles", "type": "boolean", "default":false, "description": "Whether or not to include all artists irrespective of the role they contributed. When true it overrides any role filter value." } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "artists": { "type": "array", + "items": { "$ref": "Audio.Details.Artist" } + } + } + } + }, + "AudioLibrary.GetArtistDetails": { + "type": "method", + "description": "Retrieve details about a specific artist", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "artistid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "Audio.Fields.Artist" } + ], + "returns": { "type": "object", + "properties": { + "artistdetails": { "$ref": "Audio.Details.Artist" } + } + } + }, + "AudioLibrary.GetAlbums": { + "type": "method", + "description": "Retrieve all albums from specified artist (and role) or that has songs of the specified genre", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Audio.Fields.Album" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" }, + { "name": "filter", + "type": [ + { "type": "object", "properties": { "genreid": { "$ref": "Library.Id", "required": true, "description": "Song genre. Filter for existence of songs with this genre" } }, "additionalProperties": false }, + { "type": "object", "properties": { "genre": { "type": "string", "minLength": 1, "required": true, "description": "Song genre. Filter for existence of songs with this genre" } }, "additionalProperties": false }, + { "type": "object", "properties": { "artistid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "artistid": { "$ref": "Library.Id", "required": true }, + "roleid": { "$ref": "Library.Id", "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "artistid": { "$ref": "Library.Id", "required": true }, + "role": { "type": "string", "minLength": 1, "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "artist": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "artist": { "type": "string", "minLength": 1, "required": true }, + "roleid": { "$ref": "Library.Id", "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "artist": { "type": "string", "minLength": 1, "required": true }, + "role": { "type": "string", "minLength": 1, "required": true }}, "additionalProperties": false }, + { "$ref": "List.Filter.Albums" } + ] + }, + { "name": "includesingles", "type": "boolean", "default": false }, + { "name": "allroles", "type": "boolean", "default":false, "description": "Whether or not to include all roles when filtering by artist, rather than the default of excluding other contributions. When true it overrides any role filter value." } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "albums": { "type": "array", + "items": { "$ref": "Audio.Details.Album" } + } + } + } + }, + "AudioLibrary.GetAlbumDetails": { + "type": "method", + "description": "Retrieve details about a specific album", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "albumid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "Audio.Fields.Album" } + ], + "returns": { "type": "object", + "properties": { + "albumdetails": { "$ref": "Audio.Details.Album" } + } + } + }, + "AudioLibrary.GetSongs": { + "type": "method", + "description": "Retrieve all songs from specified album, artist or genre", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Audio.Fields.Song" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" }, + { "name": "filter", + "type": [ + { "type": "object", "properties": { "genreid": { "$ref": "Library.Id", "required": true, "description": "Song genre. Filter for existence of songs with this genre" } }, "additionalProperties": false }, + { "type": "object", "properties": { "genre": { "type": "string", "minLength": 1, "required": true, "description": "Song genre. Filter for existence of songs with this genre" } }, "additionalProperties": false }, + { "type": "object", "properties": { "artistid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "artistid": { "$ref": "Library.Id", "required": true }, + "roleid": { "$ref": "Library.Id", "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "artistid": { "$ref": "Library.Id", "required": true }, + "role": { "type": "string", "minLength": 1, "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "artist": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "artist": { "type": "string", "minLength": 1, "required": true }, + "roleid": { "$ref": "Library.Id", "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "artist": { "type": "string", "minLength": 1, "required": true }, + "role": { "type": "string", "minLength": 1, "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "albumid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "album": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "$ref": "List.Filter.Songs" } + ] + }, + { "name": "includesingles", "type": "boolean", "default": true, "description": "Only songs from albums are returned when false, but overridden when singlesonly parameter is true" }, + { "name": "allroles", "type": "boolean", "default":false, "description": "Whether or not to include all roles when filtering by artist, rather than default of excluding other contributors. When true it overrides any role filter value." }, + { "name": "singlesonly", "type": "boolean", "default": false, "description": "Only singles are returned when true, and overrides includesingles parameter" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "songs": { "type": "array", + "items": { "$ref": "Audio.Details.Song" } + } + } + } + }, + "AudioLibrary.GetSongDetails": { + "type": "method", + "description": "Retrieve details about a specific song", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "songid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "Audio.Fields.Song" } + ], + "returns": { "type": "object", + "properties": { + "songdetails": { "$ref": "Audio.Details.Song" } + } + } + }, + "AudioLibrary.GetRecentlyAddedAlbums": { + "type": "method", + "description": "Retrieve recently added albums", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Audio.Fields.Album" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "albums": { "type": "array", + "items": { "$ref": "Audio.Details.Album" } + } + } + } + }, + "AudioLibrary.GetRecentlyAddedSongs": { + "type": "method", + "description": "Retrieve recently added songs", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "albumlimit", "$ref": "List.Amount", "description": "The amount of recently added albums from which to return the songs" }, + { "name": "properties", "$ref": "Audio.Fields.Song" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "songs": { "type": "array", + "items": { "$ref": "Audio.Details.Song" } + } + } + } + }, + "AudioLibrary.GetRecentlyPlayedAlbums": { + "type": "method", + "description": "Retrieve recently played albums", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Audio.Fields.Album" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "albums": { "type": "array", + "items": { "$ref": "Audio.Details.Album" } + } + } + } + }, + "AudioLibrary.GetRecentlyPlayedSongs": { + "type": "method", + "description": "Retrieve recently played songs", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Audio.Fields.Song" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "songs": { "type": "array", + "items": { "$ref": "Audio.Details.Song" } + } + } + } + }, + "AudioLibrary.GetGenres": { + "type": "method", + "description": "Retrieve all genres", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Library.Fields.Genre" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "genres": { "type": "array", "required": true, + "items": { "$ref": "Library.Details.Genre" } + } + } + } + }, + "AudioLibrary.GetSources": { + "type": "method", + "description": "Get all music sources, including unique ID", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Library.Fields.Source" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "sources": { "type": "array", "required": true, + "items": { "$ref": "Library.Details.Source" } + } + } + } + }, + "AudioLibrary.GetRoles": { + "type": "method", + "description": "Retrieve all contributor roles", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Audio.Fields.Role" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "roles": { "type": "array", "required": true, + "items": { "$ref": "Audio.Details.Role" } + } + } + } + }, + "AudioLibrary.GetAvailableArtTypes": { + "type": "method", + "description": "Retrieve a list of potential art types for a media item", + "transport": "Response", + "permission": "ReadData", + "params": [ + { + "name": "item", "required": true, + "type": [ + { "type": "object", "properties": { "albumid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "artistid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false } + ] + } + ], + "returns": { + "type": "object", + "properties": { + "availablearttypes": { "type": "array", "required": true, + "items": { "type": "string" } + } + } + } + }, + "AudioLibrary.GetAvailableArt": { + "type": "method", + "description": "Retrieve all potential art URLs for a media item by art type", + "transport": "Response", + "permission": "ReadData", + "params": [ + { + "name": "item", "required": true, + "type": [ + { "type": "object", "properties": { "albumid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "artistid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false } + ] + }, + { "name": "arttype", "type": "string" } + ], + "returns": { + "type": "object", + "properties": { + "availableart": { "type": "array", "required": true, + "items": { + "type": "object", + "properties": { + "url": { "type": "string", "description": "URL to the original image", "required": true }, + "arttype": { "type": "string", "required": true }, + "previewurl": { "type": "string", "description": "URL to a preview thumbnail of the image" } + } + } + } + } + } + }, + "AudioLibrary.SetArtistDetails": { + "type": "method", + "description": "Update the given artist with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "artistid", "$ref": "Library.Id", "required": true }, + { "name": "artist", "$ref": "Optional.String" }, + { "name": "instrument", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "style", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "mood", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "born", "$ref": "Optional.String" }, + { "name": "formed", "$ref": "Optional.String" }, + { "name": "description", "$ref": "Optional.String" }, + { "name": "genre", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "died", "$ref": "Optional.String" }, + { "name": "disbanded", "$ref": "Optional.String" }, + { "name": "yearsactive", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "musicbrainzartistid", "$ref": "Optional.String" }, + { "name": "sortname", "$ref": "Optional.String" }, + { "name": "type", "$ref": "Optional.String" }, + { "name": "gender", "$ref": "Optional.String" }, + { "name": "disambiguation", "$ref": "Optional.String" }, + { "name": "art", "type": [ "null", { "$ref": "Media.Artwork.Set", "required": true } ], "default": null } + ], + "returns": "string" + }, + "AudioLibrary.SetAlbumDetails": { + "type": "method", + "description": "Update the given album with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "albumid", "$ref": "Library.Id", "required": true }, + { "name": "title", "$ref": "Optional.String" }, + { "name": "artist", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "description", "$ref": "Optional.String" }, + { "name": "genre", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "theme", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "mood", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "style", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "type", "$ref": "Optional.String" }, + { "name": "albumlabel", "$ref": "Optional.String" }, + { "name": "rating", "$ref": "Optional.Number" }, + { "name": "year", "$ref": "Optional.Integer" }, + { "name": "userrating", "$ref": "Optional.Integer" }, + { "name": "votes", "$ref": "Optional.Integer" }, + { "name": "musicbrainzalbumid", "$ref": "Optional.String" }, + { "name": "musicbrainzreleasegroupid", "$ref": "Optional.String" }, + { "name": "sortartist", "$ref": "Optional.String" }, + { "name": "displayartist", "$ref": "Optional.String" }, + { "name": "musicbrainzalbumartistid", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "art", "type": [ "null", { "$ref": "Media.Artwork.Set", "required": true } ], "default": null }, + { "name": "isboxset", "$ref": "Optional.Boolean" }, + { "name": "releasedate", "$ref": "Optional.String" }, + { "name": "originaldate", "$ref": "Optional.String" } + ], + "returns": "string" + }, + "AudioLibrary.SetSongDetails": { + "type": "method", + "description": "Update the given song with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "songid", "$ref": "Library.Id", "required": true }, + { "name": "title", "$ref": "Optional.String" }, + { "name": "artist", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "genre", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "year", "$ref": "Optional.Integer" }, + { "name": "rating", "$ref": "Optional.Number" }, + { "name": "track", "$ref": "Optional.Integer" }, + { "name": "disc", "$ref": "Optional.Integer" }, + { "name": "duration", "$ref": "Optional.Integer" }, + { "name": "comment", "$ref": "Optional.String" }, + { "name": "musicbrainztrackid", "$ref": "Optional.String" }, + { "name": "musicbrainzartistid", "$ref": "Optional.String" }, + { "name": "playcount", "$ref": "Optional.Integer" }, + { "name": "lastplayed", "$ref": "Optional.String" }, + { "name": "userrating", "$ref": "Optional.Integer" }, + { "name": "votes", "$ref": "Optional.Integer" }, + { "name": "displayartist", "$ref": "Optional.String" }, + { "name": "sortartist", "$ref": "Optional.String" }, + { "name": "mood", "$ref": "Optional.String" }, + { "name": "art", "type": [ "null", { "$ref": "Media.Artwork.Set", "required": true } ], "default": null }, + { "name": "disctitle", "$ref": "Optional.String" }, + { "name": "releasedate", "$ref": "Optional.String" }, + { "name": "originaldate", "$ref": "Optional.String" }, + { "name": "bpm", "$ref": "Optional.Integer" } + ], + "returns": "string" + }, + "AudioLibrary.Scan": { + "type": "method", + "description": "Scans the audio sources for new library items", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "directory", "type": "string", "default": "" }, + { "name": "showdialogs", "type": "boolean", "default": true, "description": "Whether or not to show the progress bar or any other GUI dialog" } + ], + "returns": "string" + }, + "AudioLibrary.Export": { + "type": "method", + "description": "Exports all items from the audio library", + "transport": "Response", + "permission": "WriteFile", + "params": [ + { "name": "options", "type": [ + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "path": { "type": "string", "required": true, "minLength": 1, "description": "Path to the directory to where the data should be exported" } + } + }, + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "overwrite": { "type": "boolean", "default": false, "description": "Whether to overwrite existing exported files" }, + "images": { "type": "boolean", "default": false, "description": "Whether to export thumbnails and fanart images" } + } + } + ] + } + ], + "returns": "string" + }, + "AudioLibrary.Clean": { + "type": "method", + "description": "Cleans the audio library from non-existent items", + "transport": "Response", + "permission": "RemoveData", + "params": [ + { "name": "showdialogs", "type": "boolean", "default": true, "description": "Whether or not to show the progress bar or any other GUI dialog" } + ], + "returns": "string" + }, + "VideoLibrary.GetMovies": { + "type": "method", + "description": "Retrieve all movies", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Video.Fields.Movie" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" }, + { "name": "filter", + "type": [ + { "type": "object", "properties": { "genreid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "genre": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "year": { "type": "integer", "minimum": 0, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "actor": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "director": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "studio": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "country": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "setid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "set": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "tag": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "$ref": "List.Filter.Movies" } + ] + } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "movies": { "type": "array", + "items": { "$ref": "Video.Details.Movie" } + } + } + } + }, + "VideoLibrary.GetMovieDetails": { + "type": "method", + "description": "Retrieve details about a specific movie", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "movieid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "Video.Fields.Movie" } + ], + "returns": { "type": "object", + "properties": { + "moviedetails": { "$ref": "Video.Details.Movie" } + } + } + }, + "VideoLibrary.GetMovieSets": { + "type": "method", + "description": "Retrieve all movie sets", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Video.Fields.MovieSet" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "sets": { "type": "array", + "items": { "$ref": "Video.Details.MovieSet" } + } + } + } + }, + "VideoLibrary.GetMovieSetDetails": { + "type": "method", + "description": "Retrieve details about a specific movie set", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "setid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "Video.Fields.MovieSet" }, + { "name": "movies", "type": "object", + "properties": { + "properties": { "$ref": "Video.Fields.Movie" }, + "limits": { "$ref": "List.Limits" }, + "sort": { "$ref": "List.Sort" } + } + } + ], + "returns": { "type": "object", + "properties": { + "setdetails": { "$ref": "Video.Details.MovieSet.Extended" } + } + } + }, + "VideoLibrary.GetTVShows": { + "type": "method", + "description": "Retrieve all tv shows", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Video.Fields.TVShow" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" }, + { "name": "filter", + "type": [ + { "type": "object", "properties": { "genreid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "genre": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "year": { "type": "integer", "minimum": 0, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "actor": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "studio": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "tag": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "$ref": "List.Filter.TVShows" } + ] + } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "tvshows": { "type": "array", + "items": { "$ref": "Video.Details.TVShow" } + } + } + } + }, + "VideoLibrary.GetTVShowDetails": { + "type": "method", + "description": "Retrieve details about a specific tv show", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "tvshowid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "Video.Fields.TVShow" } + ], + "returns": { "type": "object", + "properties": { + "tvshowdetails": { "$ref": "Video.Details.TVShow" } + } + } + }, + "VideoLibrary.GetSeasons": { + "type": "method", + "description": "Retrieve all tv seasons", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "tvshowid", "$ref": "Library.Id" }, + { "name": "properties", "$ref": "Video.Fields.Season" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "seasons": { "type": "array", + "items": { "$ref": "Video.Details.Season" } + } + } + } + }, + "VideoLibrary.GetSeasonDetails": { + "type": "method", + "description": "Retrieve details about a specific tv show season", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "seasonid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "Video.Fields.Season" } + ], + "returns": { "type": "object", + "properties": { + "seasondetails": { "$ref": "Video.Details.Season" } + } + } + }, + "VideoLibrary.GetEpisodes": { + "type": "method", + "description": "Retrieve all tv show episodes", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "tvshowid", "$ref": "Library.Id" }, + { "name": "season", "type": "integer", "minimum": 0, "default": -1 }, + { "name": "properties", "$ref": "Video.Fields.Episode" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" }, + { "name": "filter", + "type": [ + { "type": "object", "properties": { "genreid": { "$ref": "Library.Id", "required": true, "description": "Requires tvshowid to be set" } }, "additionalProperties": false }, + { "type": "object", "properties": { "genre": { "type": "string", "minLength": 1, "required": true, "description": "Requires tvshowid to be set" } }, "additionalProperties": false }, + { "type": "object", "properties": { "year": { "type": "integer", "minimum": 0, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "actor": { "type": "string", "minLength": 1, "required": true, "description": "Requires tvshowid to be set" } }, "additionalProperties": false }, + { "type": "object", "properties": { "director": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "$ref": "List.Filter.Episodes" } + ] + } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "episodes": { "type": "array", + "items": { "$ref": "Video.Details.Episode" } + } + } + } + }, + "VideoLibrary.GetEpisodeDetails": { + "type": "method", + "description": "Retrieve details about a specific tv show episode", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "episodeid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "Video.Fields.Episode" } + ], + "returns": { "type": "object", + "properties": { + "episodedetails": { "$ref": "Video.Details.Episode" } + } + } + }, + "VideoLibrary.GetMusicVideos": { + "type": "method", + "description": "Retrieve all music videos", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Video.Fields.MusicVideo" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" }, + { "name": "filter", + "type": [ + { "type": "object", "properties": { "artist": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "genreid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "genre": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "year": { "type": "integer", "minimum": 0, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "director": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "studio": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "tag": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "$ref": "List.Filter.MusicVideos" } + ] + } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "musicvideos": { "type": "array", + "items": { "$ref": "Video.Details.MusicVideo" } + } + } + } + }, + "VideoLibrary.GetMusicVideoDetails": { + "type": "method", + "description": "Retrieve details about a specific music video", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "musicvideoid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "Video.Fields.MusicVideo" } + ], + "returns": { "type": "object", + "properties": { + "musicvideodetails": { "$ref": "Video.Details.MusicVideo" } + } + } + }, + "VideoLibrary.GetRecentlyAddedMovies": { + "type": "method", + "description": "Retrieve all recently added movies", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Video.Fields.Movie" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "movies": { "type": "array", + "items": { "$ref": "Video.Details.Movie" } + } + } + } + }, + "VideoLibrary.GetRecentlyAddedEpisodes": { + "type": "method", + "description": "Retrieve all recently added tv episodes", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Video.Fields.Episode" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "episodes": { "type": "array", + "items": { "$ref": "Video.Details.Episode" } + } + } + } + }, + "VideoLibrary.GetRecentlyAddedMusicVideos": { + "type": "method", + "description": "Retrieve all recently added music videos", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Video.Fields.MusicVideo" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "musicvideos": { "type": "array", + "items": { "$ref": "Video.Details.MusicVideo" } + } + } + } + }, + "VideoLibrary.GetInProgressTVShows": { + "type": "method", + "description": "Retrieve all in progress tvshows", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Video.Fields.TVShow" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "tvshows": { "type": "array", + "items": { "$ref": "Video.Details.TVShow" } + } + } + } + }, + "VideoLibrary.GetGenres": { + "type": "method", + "description": "Retrieve all genres", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "type", "type": "string", "required": true, "enum": [ "movie", "tvshow", "musicvideo"] }, + { "name": "properties", "$ref": "Library.Fields.Genre" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "genres": { "type": "array", "required": true, + "items": { "$ref": "Library.Details.Genre" } + } + } + } + }, + "VideoLibrary.GetTags": { + "type": "method", + "description": "Retrieve all tags", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "type", "type": "string", "required": true, "enum": [ "movie", "tvshow", "musicvideo" ] }, + { "name": "properties", "$ref": "Library.Fields.Tag" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "tags": { "type": "array", "required": true, + "items": { "$ref": "Library.Details.Tag" } + } + } + } + }, + "VideoLibrary.GetAvailableArtTypes": { + "type": "method", + "description": "Retrieve a list of potential art types for a media item", + "transport": "Response", + "permission": "ReadData", + "params": [ + { + "name": "item", "required": true, + "type": [ + { "type": "object", "properties": { "episodeid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "tvshowid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "seasonid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "movieid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "setid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "musicvideoid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false } + ] + } + ], + "returns": { + "type": "object", + "properties": { + "availablearttypes": { "type": "array", "required": true, + "items": { "type": "string" } + } + } + } + }, + "VideoLibrary.GetAvailableArt": { + "type": "method", + "description": "Retrieve all potential art URLs for a media item by art type", + "transport": "Response", + "permission": "ReadData", + "params": [ + { + "name": "item", "required": true, + "type": [ + { "type": "object", "properties": { "episodeid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "tvshowid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "seasonid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "movieid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "setid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "musicvideoid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false } + ] + }, + { "name": "arttype", "type": "string" } + ], + "returns": { + "type": "object", + "properties": { + "availableart": { "type": "array", "required": true, + "items": { + "type": "object", + "properties": { + "url": { "type": "string", "description": "URL to the original image", "required": true }, + "arttype": { "type": "string", "required": true }, + "previewurl": { "type": "string", "description": "URL to a preview thumbnail of the image" } + } + } + } + } + } + }, + "VideoLibrary.SetMovieDetails": { + "type": "method", + "description": "Update the given movie with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "movieid", "$ref": "Library.Id", "required": true }, + { "name": "title", "$ref": "Optional.String" }, + { "name": "playcount", "$ref": "Optional.Integer" }, + { "name": "runtime", "$ref": "Optional.Integer", "description": "Runtime in seconds" }, + { "name": "director", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "studio", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "year", "$ref": "Optional.Integer", "description": "linked with premiered. Overridden by premiered parameter" }, + { "name": "plot", "$ref": "Optional.String" }, + { "name": "genre", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "rating", "$ref": "Optional.Number" }, + { "name": "mpaa", "$ref": "Optional.String" }, + { "name": "imdbnumber", "$ref": "Optional.String" }, + { "name": "votes", "$ref": "Optional.String" }, + { "name": "lastplayed", "$ref": "Optional.String" }, + { "name": "originaltitle", "$ref": "Optional.String" }, + { "name": "trailer", "$ref": "Optional.String" }, + { "name": "tagline", "$ref": "Optional.String" }, + { "name": "plotoutline", "$ref": "Optional.String" }, + { "name": "writer", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "country", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "top250", "$ref": "Optional.Integer" }, + { "name": "sorttitle", "$ref": "Optional.String" }, + { "name": "set", "$ref": "Optional.String" }, + { "name": "showlink", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "thumbnail", "$ref": "Optional.String" }, + { "name": "fanart", "$ref": "Optional.String" }, + { "name": "tag", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "art", "type": [ "null", { "$ref": "Media.Artwork.Set", "required": true } ], "default": null }, + { "name": "resume", "type": [ "null", { "$ref": "Video.Resume", "required": true } ], "default": null }, + { "name": "userrating", "$ref": "Optional.Integer" }, + { "name": "ratings", "$ref": "Video.Ratings.Set" }, + { "name": "dateadded", "$ref": "Optional.String" }, + { "name": "premiered", "$ref": "Optional.String", "description": "linked with year. Overrides year" }, + { "name": "uniqueid", "type": [ "null", { "$ref": "Media.UniqueID.Set", "required": true } ], "default": null } + ], + "returns": "string" + }, + "VideoLibrary.SetMovieSetDetails": { + "type": "method", + "description": "Update the given movie set with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "setid", "$ref": "Library.Id", "required": true }, + { "name": "title", "$ref": "Optional.String" }, + { "name": "art", "type": [ "null", { "$ref": "Media.Artwork.Set", "required": true } ], "default": null }, + { "name": "plot", "$ref": "Optional.String" } + ], + "returns": "string" + }, + "VideoLibrary.SetTVShowDetails": { + "type": "method", + "description": "Update the given tvshow with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "tvshowid", "$ref": "Library.Id", "required": true }, + { "name": "title", "$ref": "Optional.String" }, + { "name": "playcount", "$ref": "Optional.Integer" }, + { "name": "studio", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "plot", "$ref": "Optional.String" }, + { "name": "genre", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "rating", "$ref": "Optional.Number" }, + { "name": "mpaa", "$ref": "Optional.String" }, + { "name": "imdbnumber", "$ref": "Optional.String" }, + { "name": "premiered", "$ref": "Optional.String" }, + { "name": "votes", "$ref": "Optional.String" }, + { "name": "lastplayed", "$ref": "Optional.String" }, + { "name": "originaltitle", "$ref": "Optional.String" }, + { "name": "sorttitle", "$ref": "Optional.String" }, + { "name": "episodeguide", "$ref": "Optional.String" }, + { "name": "thumbnail", "$ref": "Optional.String" }, + { "name": "fanart", "$ref": "Optional.String" }, + { "name": "tag", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "art", "type": [ "null", { "$ref": "Media.Artwork.Set", "required": true } ], "default": null }, + { "name": "userrating", "$ref": "Optional.Integer" }, + { "name": "ratings", "$ref": "Video.Ratings.Set" }, + { "name": "dateadded", "$ref": "Optional.String" }, + { "name": "runtime", "$ref": "Optional.Integer", "description": "Runtime in seconds" }, + { "name": "status", "$ref": "Optional.String", "description": "Valid values: 'returning series', 'in production', 'planned', 'cancelled', 'ended'" }, + { "name": "uniqueid", "type": [ "null", { "$ref": "Media.UniqueID.Set", "required": true } ], "default": null } + ], + "returns": "string" + }, + "VideoLibrary.SetSeasonDetails": { + "type": "method", + "description": "Update the given season with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "seasonid", "$ref": "Library.Id", "required": true }, + { "name": "art", "type": [ "null", { "$ref": "Media.Artwork.Set", "required": true } ], "default": null }, + { "name": "userrating", "$ref": "Optional.Integer" }, + { "name": "title", "$ref": "Optional.String" } + ], + "returns": "string" + }, + "VideoLibrary.SetEpisodeDetails": { + "type": "method", + "description": "Update the given episode with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "episodeid", "$ref": "Library.Id", "required": true }, + { "name": "title", "$ref": "Optional.String" }, + { "name": "playcount", "$ref": "Optional.Integer" }, + { "name": "runtime", "$ref": "Optional.Integer", "description": "Runtime in seconds" }, + { "name": "director", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "plot", "$ref": "Optional.String" }, + { "name": "rating", "$ref": "Optional.Number" }, + { "name": "votes", "$ref": "Optional.String" }, + { "name": "lastplayed", "$ref": "Optional.String" }, + { "name": "writer", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "firstaired", "$ref": "Optional.String" }, + { "name": "productioncode", "$ref": "Optional.String" }, + { "name": "season", "$ref": "Optional.Integer" }, + { "name": "episode", "$ref": "Optional.Integer" }, + { "name": "originaltitle", "$ref": "Optional.String" }, + { "name": "thumbnail", "$ref": "Optional.String" }, + { "name": "fanart", "$ref": "Optional.String" }, + { "name": "art", "type": [ "null", { "$ref": "Media.Artwork.Set", "required": true } ], "default": null }, + { "name": "resume", "type": [ "null", { "$ref": "Video.Resume", "required": true } ], "default": null }, + { "name": "userrating", "$ref": "Optional.Integer" }, + { "name": "ratings", "$ref": "Video.Ratings.Set" }, + { "name": "dateadded", "$ref": "Optional.String" }, + { "name": "uniqueid", "type": [ "null", { "$ref": "Media.UniqueID.Set", "required": true } ], "default": null } + ], + "returns": "string" + }, + "VideoLibrary.SetMusicVideoDetails": { + "type": "method", + "description": "Update the given music video with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "musicvideoid", "$ref": "Library.Id", "required": true }, + { "name": "title", "$ref": "Optional.String" }, + { "name": "playcount", "$ref": "Optional.Integer" }, + { "name": "runtime", "$ref": "Optional.Integer", "description": "Runtime in seconds" }, + { "name": "director", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "studio", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "year", "$ref": "Optional.Integer", "description": "linked with premiered. Overridden by premiered parameter" }, + { "name": "plot", "$ref": "Optional.String" }, + { "name": "album", "$ref": "Optional.String" }, + { "name": "artist", "type": [ "null", { "$ref": "Array.String", "required": true } ] }, + { "name": "genre", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "track", "$ref": "Optional.Integer" }, + { "name": "lastplayed", "$ref": "Optional.String" }, + { "name": "thumbnail", "$ref": "Optional.String" }, + { "name": "fanart", "$ref": "Optional.String" }, + { "name": "tag", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "art", "type": [ "null", { "$ref": "Media.Artwork.Set", "required": true } ], "default": null }, + { "name": "resume", "type": [ "null", { "$ref": "Video.Resume", "required": true } ], "default": null }, + { "name": "rating", "$ref": "Optional.Number" }, + { "name": "userrating", "$ref": "Optional.Integer" }, + { "name": "dateadded", "$ref": "Optional.String" }, + { "name": "premiered", "$ref": "Optional.String", "description": "linked with year. Overrides year" }, + { "name": "uniqueid", "type": [ "null", { "$ref": "Media.UniqueID.Set", "required": true } ], "default": null } + ], + "returns": "string" + }, + "VideoLibrary.RefreshMovie": { + "type": "method", + "description": "Refresh the given movie in the library", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "movieid", "$ref": "Library.Id", "required": true }, + { "name": "ignorenfo", "type": "boolean", "required": false, "default": false, "description": "Whether or not to ignore a local NFO if present." }, + { "name": "title", "type": "string", "required": "false", "default": "", "description": "Title to use for searching (instead of determining it from the item's filename/path)." } + ], + "returns": "string" + }, + "VideoLibrary.RefreshTVShow": { + "type": "method", + "description": "Refresh the given tv show in the library", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "tvshowid", "$ref": "Library.Id", "required": true }, + { "name": "ignorenfo", "type": "boolean", "required": false, "default": false, "description": "Whether or not to ignore a local NFO if present." }, + { "name": "refreshepisodes", "type": "boolean", "required": false, "default": false, "description": "Whether or not to refresh all episodes belonging to the TV show." }, + { "name": "title", "type": "string", "required": "false", "default": "", "description": "Title to use for searching (instead of determining it from the item's filename/path)." } + ], + "returns": "string" + }, + "VideoLibrary.RefreshEpisode": { + "type": "method", + "description": "Refresh the given episode in the library", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "episodeid", "$ref": "Library.Id", "required": true }, + { "name": "ignorenfo", "type": "boolean", "required": false, "default": false, "description": "Whether or not to ignore a local NFO if present." }, + { "name": "title", "type": "string", "required": "false", "default": "", "description": "Title to use for searching (instead of determining it from the item's filename/path)." } + ], + "returns": "string" + }, + "VideoLibrary.RefreshMusicVideo": { + "type": "method", + "description": "Refresh the given music video in the library", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "musicvideoid", "$ref": "Library.Id", "required": true }, + { "name": "ignorenfo", "type": "boolean", "required": false, "default": false, "description": "Whether or not to ignore a local NFO if present." }, + { "name": "title", "type": "string", "required": "false", "default": "", "description": "Title to use for searching (instead of determining it from the item's filename/path)." } + ], + "returns": "string" + }, + "VideoLibrary.RemoveMovie": { + "type": "method", + "description": "Removes the given movie from the library", + "transport": "Response", + "permission": "RemoveData", + "params": [ + { "name": "movieid", "$ref": "Library.Id", "required": true } + ], + "returns": "string" + }, + "VideoLibrary.RemoveTVShow": { + "type": "method", + "description": "Removes the given tv show from the library", + "transport": "Response", + "permission": "RemoveData", + "params": [ + { "name": "tvshowid", "$ref": "Library.Id", "required": true } + ], + "returns": "string" + }, + "VideoLibrary.RemoveEpisode": { + "type": "method", + "description": "Removes the given episode from the library", + "transport": "Response", + "permission": "RemoveData", + "params": [ + { "name": "episodeid", "$ref": "Library.Id", "required": true } + ], + "returns": "string" + }, + "VideoLibrary.RemoveMusicVideo": { + "type": "method", + "description": "Removes the given music video from the library", + "transport": "Response", + "permission": "RemoveData", + "params": [ + { "name": "musicvideoid", "$ref": "Library.Id", "required": true } + ], + "returns": "string" + }, + "VideoLibrary.Scan": { + "type": "method", + "description": "Scans the video sources for new library items", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "directory", "type": "string", "default": "" }, + { "name": "showdialogs", "type": "boolean", "default": true, "description": "Whether or not to show the progress bar or any other GUI dialog" } + ], + "returns": "string" + }, + "VideoLibrary.Export": { + "type": "method", + "description": "Exports all items from the video library", + "transport": "Response", + "permission": "WriteFile", + "params": [ + { "name": "options", "type": [ + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "path": { "type": "string", "required": true, "minLength": 1, "description": "Path to the directory to where the data should be exported" } + } + }, + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "overwrite": { "type": "boolean", "default": false, "description": "Whether to overwrite existing exported files" }, + "images": { "type": "boolean", "default": false, "description": "Whether to export thumbnails and fanart images" }, + "actorthumbs": { "type": "boolean", "default": false, "description": "Whether to export actor thumbnails" } + } + } + ] + } + ], + "returns": "string" + }, + "VideoLibrary.Clean": { + "type": "method", + "description": "Cleans the video library for non-existent items", + "transport": "Response", + "permission": "RemoveData", + "params": [ + { "name": "showdialogs", "type": "boolean", "default": true, "description": "Whether or not to show the progress bar or any other GUI dialog" }, + { "name": "content", "type": "string", "default": "video", "enum": [ "video", "movies", "tvshows", "musicvideos" ], "description": "Content type to clean for" }, + { "name": "directory", "type": "string", "default": "", "description": "Path to the directory to clean up; performs a global cleanup if not specified" } + ], + "returns": "string" + }, + "GUI.ActivateWindow": { + "type": "method", + "description": "Activates the given window", + "transport": "Response", + "permission": "ControlGUI", + "params": [ + { "name": "window", "$ref": "GUI.Window", "required": true }, + { "name": "parameters", "type": "array", "items": { "type": "string", "minLength": 1, "required": true }, "minItems": 1 } + ], + "returns": "string" + }, + "GUI.ShowNotification": { + "type": "method", + "description": "Shows a GUI notification", + "transport": "Response", + "permission": "ControlGUI", + "params": [ + { "name": "title", "type": "string", "required": true }, + { "name": "message", "type": "string", "required": true }, + { "name": "image", "type": [ + { "type": "string", "required": true, "enum": [ "info", "warning", "error" ] }, + { "type": "string", "required": true } + ], "default": "" + }, + { "name": "displaytime", "type": "integer", "minimum": 1500, "default": 5000, "description": "The time in milliseconds the notification will be visible" } + ], + "returns": "string" + }, + "GUI.GetProperties": { + "type": "method", + "description": "Retrieves the values of the given properties", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "type": "array", "uniqueItems": true, "required": true, "items": { "$ref": "GUI.Property.Name" } } + ], + "returns": { "$ref": "GUI.Property.Value", "required": true } + }, + "GUI.SetFullscreen": { + "type": "method", + "description": "Toggle fullscreen/GUI", + "transport": "Response", + "permission": "ControlGUI", + "params": [ + { "name": "fullscreen", "required": true, "$ref": "Global.Toggle" } + ], + "returns": { "type": "boolean", "description": "Fullscreen state" } + }, + "GUI.SetStereoscopicMode": { + "type": "method", + "description": "Sets the stereoscopic mode of the GUI to the given mode", + "transport": "Response", + "permission": "ControlGUI", + "params": [ + { "name": "mode", "type": "string", "enum": [ "toggle", "tomono", "next", "previous", "select", "off", "split_vertical", "split_horizontal", "row_interleaved", "hardware_based", "anaglyph_cyan_red", "anaglyph_green_magenta", "monoscopic" ], "required": true } + ], + "returns": "string" + }, + "GUI.GetStereoscopicModes": { + "type": "method", + "description": "Returns the supported stereoscopic modes of the GUI", + "transport": "Response", + "permission": "ReadData", + "params": [], + "returns": { + "type": "object", + "properties": { + "stereoscopicmodes" : { + "type": "array", + "uniqueItems": true, + "items": { "$ref": "GUI.Stereoscopy.Mode" } + } + } + } + }, + "Addons.GetAddons": { + "type": "method", + "description": "Gets all available addons", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "type", "$ref": "Addon.Types" }, + { "name": "content", "$ref": "Addon.Content", "description": "Content provided by the addon. Only considered for plugins and scripts." }, + { "name": "enabled", "type": [ { "type": "boolean" }, { "type": "string", "enum": [ "all" ] } ], "default": "all" }, + { "name": "properties", "$ref": "Addon.Fields" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "installed", "type": [ { "type": "boolean" }, { "type": "string", "enum": [ "all" ] } ], "default": true } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "addons": { "type": "array", + "items": { "$ref": "Addon.Details" } + } + } + } + }, + "Addons.GetAddonDetails": { + "type": "method", + "description": "Gets the details of a specific addon", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "addonid", "type": "string", "required": true }, + { "name": "properties", "$ref": "Addon.Fields" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "addon": { "$ref": "Addon.Details", "required": true } + } + } + }, + "Addons.SetAddonEnabled": { + "type": "method", + "description": "Enables/Disables a specific addon", + "transport": "Response", + "permission": "ManageAddon", + "params": [ + { "name": "addonid", "type": "string", "required": true }, + { "name": "enabled", "$ref": "Global.Toggle", "required": true } + ], + "returns": "string" + }, + "Addons.ExecuteAddon": { + "type": "method", + "description": "Executes the given addon with the given parameters (if possible)", + "transport": "Response", + "permission": "ExecuteAddon", + "params": [ + { "name": "addonid", "type": "string", "required": true }, + { "name": "params", "type": [ + { "type": "object", "additionalProperties": { "type": "string" } }, + { "type": "array", "items": { "type": "string" } }, + { "type": "string", "description": "URL path (must start with / or ?" } + ], + "default": "" + }, + { "name": "wait", "type": "boolean", "default": false } + ], + "returns": "string" + }, + "PVR.GetProperties": { + "type": "method", + "description": "Retrieves the values of the given properties", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "type": "array", "uniqueItems": true, "required": true, "items": { "$ref": "PVR.Property.Name" } } + ], + "returns": { "$ref": "PVR.Property.Value", "required": true } + }, + "PVR.GetChannelGroups": { + "type": "method", + "description": "Retrieves the channel groups for the specified type", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "channeltype", "$ref": "PVR.Channel.Type", "required": true }, + { "name": "limits", "$ref": "List.Limits" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "channelgroups": { "type": "array", "required": true, + "items": { "$ref": "PVR.Details.ChannelGroup" } + } + } + } + }, + "PVR.GetChannelGroupDetails": { + "type": "method", + "description": "Retrieves the details of a specific channel group", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "channelgroupid", "$ref": "PVR.ChannelGroup.Id", "required": true }, + { "name": "channels", "type": "object", + "properties": { + "properties": { "$ref": "PVR.Fields.Channel" }, + "limits": { "$ref": "List.Limits" } + } + } + ], + "returns": { "type": "object", + "properties": { + "channelgroupdetails": { "$ref": "PVR.Details.ChannelGroup.Extended" } + } + } + }, + "PVR.GetChannels": { + "type": "method", + "description": "Retrieves the channel list", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "channelgroupid", "$ref": "PVR.ChannelGroup.Id", "required": true }, + { "name": "properties", "$ref": "PVR.Fields.Channel" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "channels": { "type": "array", "required": true, + "items": { "$ref": "PVR.Details.Channel" } + } + } + } + }, + "PVR.GetChannelDetails": { + "type": "method", + "description": "Retrieves the details of a specific channel", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "channelid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "PVR.Fields.Channel" } + ], + "returns": { "type": "object", + "properties": { + "channeldetails": { "$ref": "PVR.Details.Channel" } + } + } + }, + "PVR.GetClients": { + "type": "method", + "description": "Retrieves the enabled PVR clients and their capabilities", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "limits", "$ref": "List.Limits" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "clients": { "type": "array", "required": true, + "items": { "$ref": "PVR.Details.Client" } + } + } + } + }, + "PVR.GetBroadcasts": { + "type": "method", + "description": "Retrieves the program of a specific channel", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "channelid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "PVR.Fields.Broadcast" }, + { "name": "limits", "$ref": "List.Limits" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "broadcasts": { "type": "array", "required": true, + "items": { "$ref": "PVR.Details.Broadcast" } + } + } + } + }, + "PVR.GetBroadcastDetails": { + "type": "method", + "description": "Retrieves the details of a specific broadcast", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "broadcastid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "PVR.Fields.Broadcast" } + ], + "returns": { "type": "object", + "properties": { + "broadcastdetails": { "$ref": "PVR.Details.Broadcast" } + } + } + }, + "PVR.GetBroadcastIsPlayable": { + "type": "method", + "description": "Retrieves whether or not a broadcast is playable", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "broadcastid", "$ref": "Library.Id", "required": true, "description": "the id of the broadcast to check for playability" } + ], + "returns": "boolean" + }, + "PVR.GetTimers": { + "type": "method", + "description": "Retrieves the timers", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "PVR.Fields.Timer" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "timers": { "type": "array", "required": true, + "items": { "$ref": "PVR.Details.Timer" } + } + } + } + }, + "PVR.GetTimerDetails": { + "type": "method", + "description": "Retrieves the details of a specific timer", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "timerid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "PVR.Fields.Timer" } + ], + "returns": { "type": "object", + "properties": { + "timerdetails": { "$ref": "PVR.Details.Timer" } + } + } + }, + "PVR.AddTimer": { + "type": "method", + "description": "Adds a timer to record the given show one times or a timer rule to record all showings of the given show or adds a reminder timer or reminder timer rule", + "transport": "Response", + "permission": "ControlPVR", + "params": [ + { "name": "broadcastid", "$ref": "Library.Id", "required": true, "description": "the broadcast id of the item to record" }, + { "name": "timerrule", "type": "boolean", "default": false, "description": "controls whether to create a timer rule or a onetime timer" }, + { "name": "reminder", "type": "boolean", "default": false, "description": "controls whether to create a reminder timer or a recording timer" } + ], + "returns": "string" + }, + "PVR.DeleteTimer": { + "type": "method", + "description": "Deletes a onetime timer or a timer rule", + "transport": "Response", + "permission": "ControlPVR", + "params": [ + { "name": "timerid", "$ref": "Library.Id", "required": true, "description": "the id of the onetime timer or timer rule to delete" } + ], + "returns": "string" + }, + "PVR.ToggleTimer": { + "type": "method", + "description": "Creates or deletes a onetime timer or timer rule for a given show. If it exists, it will be deleted. If it does not exist, it will be created", + "transport": "Response", + "permission": "ControlPVR", + "params": [ + { "name": "broadcastid", "$ref": "Library.Id", "required": true, "description": "the broadcast id of the item to toggle a onetime timer or time rule for" }, + { "name": "timerrule", "type": "boolean", "default": false, "description": "controls whether to create / delete a timer rule or a onetime timer" } + ], + "returns": "string" + }, + "PVR.GetRecordings": { + "type": "method", + "description": "Retrieves the recordings", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "PVR.Fields.Recording" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "recordings": { "type": "array", "required": true, + "items": { "$ref": "PVR.Details.Recording" } + } + } + } + }, + "PVR.GetRecordingDetails": { + "type": "method", + "description": "Retrieves the details of a specific recording", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "recordingid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "PVR.Fields.Recording" } + ], + "returns": { "type": "object", + "properties": { + "recordingdetails": { "$ref": "PVR.Details.Recording" } + } + } + }, + "PVR.Record": { + "type": "method", + "description": "Toggle recording of a channel", + "transport": "Response", + "permission": "ControlPVR", + "params": [ + { "name": "record", "$ref": "Global.Toggle", "default": "toggle" }, + { "name": "channel", "type": [ + { "type": "string", "enum": [ "current" ], "required": true }, + { "$ref": "Library.Id", "required": true } + ], + "default": "current" + } + ], + "returns": "string" + }, + "PVR.Scan": { + "type": "method", + "description": "Starts a channel scan", + "transport": "Response", + "permission": "ControlPVR", + "params": [ + { "name": "clientid", "$ref": "Library.Id", "description": "Specify a PVR client id to avoid UI dialog, optional in kodi 19, required in kodi 20" } + ], + "returns": "string" + }, + "Textures.GetTextures": { + "type": "method", + "description": "Retrieve all textures", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Textures.Fields.Texture" }, + { "name": "filter", "$ref": "List.Filter.Textures" } + ], + "returns": { + "type": "object", + "properties": { + "textures": { "type": "array", "required": true, + "items": { "$ref": "Textures.Details.Texture" } + } + } + } + }, + "Textures.RemoveTexture": { + "type": "method", + "description": "Remove the specified texture", + "transport": "Response", + "permission": "RemoveData", + "params": [ + { "name": "textureid", "$ref": "Library.Id", "required": true, "description": "Texture database identifier" } + ], + "returns": "string" + }, + "Profiles.GetProfiles": { + "type": "method", + "description": "Retrieve all profiles", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Profiles.Fields.Profile" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "profiles": { "type": "array", "required": true, + "items": { "$ref": "Profiles.Details.Profile" } + } + } + } + }, + "Profiles.GetCurrentProfile": { + "type": "method", + "description": "Retrieve the current profile", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Profiles.Fields.Profile" } + ], + "returns": { "$ref": "Profiles.Details.Profile", "required": true } + }, + "Profiles.LoadProfile": { + "type": "method", + "description": "Load the specified profile", + "transport": "Response", + "permission": "Navigate", + "params": [ + { "name": "profile", "type": "string", "required": true, "description": "Profile name" }, + { "name": "prompt", "type": "boolean", "description": "Prompt for password" }, + { "name": "password", "$ref": "Profiles.Password" } + ], + "returns": "string" + }, + "System.GetProperties": { + "type": "method", + "description": "Retrieves the values of the given properties", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "type": "array", "uniqueItems": true, "required": true, "items": { "$ref": "System.Property.Name" } } + ], + "returns": { "$ref": "System.Property.Value", "required": true } + }, + "System.EjectOpticalDrive": { + "type": "method", + "description": "Ejects or closes the optical disc drive (if available)", + "transport": "Response", + "permission": "ControlSystem", + "params": [ ], + "returns": "string" + }, + "System.Shutdown": { + "type": "method", + "description": "Shuts the system running Kodi down", + "transport": "Response", + "permission": "ControlPower", + "params": [], + "returns": "string" + }, + "System.Suspend": { + "type": "method", + "description": "Suspends the system running Kodi", + "transport": "Response", + "permission": "ControlPower", + "params": [], + "returns": "string" + }, + "System.Hibernate": { + "type": "method", + "description": "Puts the system running Kodi into hibernate mode", + "transport": "Response", + "permission": "ControlPower", + "params": [], + "returns": "string" + }, + "System.Reboot": { + "type": "method", + "description": "Reboots the system running Kodi", + "transport": "Response", + "permission": "ControlPower", + "params": [], + "returns": "string" + }, + "Input.SendText": { + "type": "method", + "description": "Send a generic (unicode) text", + "transport": "Response", + "permission": "Navigate", + "params": [ + { "name": "text", "type": "string", "required": true, "description": "Unicode text" }, + { "name": "done", "type": "boolean", "default": true, "description": "Whether this is the whole input or not (closes an open input dialog if true)." } + ], + "returns": "string" + }, + "Input.ExecuteAction": { + "type": "method", + "description": "Execute a specific action", + "transport": "Response", + "permission": "Navigate", + "params": [ + { "name": "action", "$ref": "Input.Action", "required": true } + ], + "returns": "string" + }, + "Input.ButtonEvent": { + "type": "method", + "description": "Send a button press event", + "transport": "Response", + "permission": "Navigate", + "params": [ + { "name": "button", "type": "string", "required": true, "description": "Button name" }, + { "name": "keymap", "type": "string", "required": true, "description": "Keymap name (KB, XG, R1, or R2)", "enum": [ "KB", "XG", "R1", "R2" ] }, + { "name": "holdtime", "type": "integer", "required": false, "minimum" : 0, "default" : 0, "description": "Number of milliseconds to simulate button hold." } + ], + "returns": "string" + }, + "Input.Left": { + "type": "method", + "description": "Navigate left in GUI", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.Right": { + "type": "method", + "description": "Navigate right in GUI", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.Down": { + "type": "method", + "description": "Navigate down in GUI", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.Up": { + "type": "method", + "description": "Navigate up in GUI", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.Select": { + "type": "method", + "description": "Select current item in GUI", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.Back": { + "type": "method", + "description": "Goes back in GUI", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.ContextMenu": { + "type": "method", + "description": "Shows the context menu", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.Info": { + "type": "method", + "description": "Shows the information dialog", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.Home": { + "type": "method", + "description": "Goes to home window in GUI", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.ShowCodec": { + "type": "method", + "description": "Show codec information of the playing item", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.ShowOSD": { + "type": "method", + "description": "Show the on-screen display for the current player", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.ShowPlayerProcessInfo": { + "type": "method", + "description": "Show player process information of the playing item, like video decoder, pixel format, pvr signal strength, ...", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Application.GetProperties": { + "type": "method", + "description": "Retrieves the values of the given properties", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "type": "array", "uniqueItems": true, "required": true, "items": { "$ref": "Application.Property.Name" } } + ], + "returns": { "$ref": "Application.Property.Value", "required": true } + }, + "Application.SetVolume": { + "type": "method", + "description": "Set the current volume", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "volume", "type": [ + { "type": "integer", "minimum": 0, "maximum": 100, "required": true }, + { "$ref": "Global.IncrementDecrement", "required": true } + ], + "required": true + } + ], + "returns": "integer" + }, + "Application.SetMute": { + "type": "method", + "description": "Toggle mute/unmute", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "mute", "required": true, "$ref": "Global.Toggle" } + ], + "returns": { "type": "boolean", "description": "Mute state" } + }, + "Application.Quit": { + "type": "method", + "description": "Quit application", + "transport": "Response", + "permission": "ControlPower", + "params": [], + "returns": "string" + }, + "XBMC.GetInfoLabels": { + "type": "method", + "description": "Retrieve info labels about Kodi and the system", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "labels", "type": "array", "required": true, "items": { "type": "string" }, "minItems": 1, "description": "See http://kodi.wiki/view/InfoLabels for a list of possible info labels" } + ], + "returns": { + "type": "object", + "description": "Object containing key-value pairs of the retrieved info labels", + "additionalProperties": { "type": "string" } + } + }, + "XBMC.GetInfoBooleans": { + "type": "method", + "description": "Retrieve info booleans about Kodi and the system", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "booleans", "type": "array", "required": true, "items": { "type": "string" }, "minItems": 1 } + ], + "returns": { + "type": "object", + "description": "Object containing key-value pairs of the retrieved info booleans", + "additionalProperties": { "type": "string" } + } + }, + "Favourites.GetFavourites": { + "type": "method", + "description": "Retrieve all favourites", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "type", "type": [ "null", { "$ref": "Favourite.Type" } ], "default": null }, + { "name": "properties", "$ref": "Favourite.Fields.Favourite" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "favourites": { "type": "array", + "items": { "$ref": "Favourite.Details.Favourite" } + } + } + } + }, + "Favourites.AddFavourite": { + "type": "method", + "description": "Add a favourite with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "title", "type": "string", "required": true }, + { "name": "type", "$ref": "Favourite.Type", "required": true }, + { "name": "path", "$ref": "Optional.String", "description": "Required for media, script and androidapp favourites types" }, + { "name": "window", "$ref": "Optional.String", "description": "Required for window favourite type" }, + { "name": "windowparameter", "$ref": "Optional.String" }, + { "name": "thumbnail", "$ref": "Optional.String" } + ], + "returns": "string" + }, + "Settings.GetSections": { + "type": "method", + "description": "Retrieves all setting sections", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "level", "$ref": "Setting.Level", "default": "standard" }, + { "name": "properties", "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "categories" ] + } + } + ], + "returns": { + "type": "object", + "properties": { + "sections": { "type": "array", + "items": { "$ref": "Setting.Details.Section" } + } + } + } + }, + "Settings.GetCategories": { + "type": "method", + "description": "Retrieves all setting categories", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "level", "$ref": "Setting.Level", "default": "standard" }, + { "name": "section", "type": "string", "default": "" }, + { "name": "properties", "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "settings" ] + } + } + ], + "returns": { + "type": "object", + "properties": { + "categories": { "type": "array", + "items": { "$ref": "Setting.Details.Category" } + } + } + } + }, + "Settings.GetSettings": { + "type": "method", + "description": "Retrieves all settings", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "level", "$ref": "Setting.Level", "default": "standard" }, + { "name": "filter", "type": [ + { "type": "object", + "properties": { + "section": { "type": "string", "required": true, "minLength": 1 }, + "category": { "type": "string", "required": true, "minLength": 1 } + }, + "additionalProperties": false, + "required": true + } + ], + "default": null + } + ], + "returns": { + "type": "object", + "properties": { + "settings": { "type": "array", + "items": { "$ref": "Setting.Details.Setting" } + } + } + } + }, + "Settings.GetSettingValue": { + "type": "method", + "description": "Retrieves the value of a setting", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "setting", "type": "string", "required": true, "minLength": 1 } + ], + "returns": { + "type": "object", + "properties": { + "value": { "$ref": "Setting.Value.Extended", "required": true } + } + } + }, + "Settings.SetSettingValue": { + "type": "method", + "description": "Changes the value of a setting", + "transport": "Response", + "permission": "WriteSetting", + "params": [ + { "name": "setting", "type": "string", "required": true, "minLength": 1 }, + { "name": "value", "$ref": "Setting.Value.Extended", "required": true } + ], + "returns": "boolean" + }, + "Settings.ResetSettingValue": { + "type": "method", + "description": "Resets the value of a setting", + "transport": "Response", + "permission": "WriteSetting", + "params": [ + { "name": "setting", "type": "string", "required": true, "minLength": 1 } + ], + "returns": "string" + }, + "Settings.GetSkinSettingValue": { + "type": "method", + "description": "Retrieves the value of the specified skin setting", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "setting", "type": "string", "required": true, "minLength": 1 } + ], + "returns": { + "type": "object", + "properties": { + "value": { "type": [ "boolean", "string" ], "required": true } + } + } + }, + "Settings.SetSkinSettingValue": { + "type": "method", + "description": "Changes the value of the specified skin setting", + "transport": "Response", + "permission": "WriteSetting", + "params": [ + { "name": "setting", "type": "string", "required": true, "minLength": 1 }, + { "name": "value", "type": [ "boolean", "string" ], "required": true } + ], + "returns": "boolean" + }, + "Settings.GetSkinSettings": { + "type": "method", + "description": "Retrieves all skin settings of the currently used skin", + "transport": "Response", + "permission": "ReadData", + "returns": { + "type": "object", + "properties": { + "skin": { "type": "string", "required": true, "minLength": 1 }, + "settings": { "type": "array", + "items": { "type": "object", + "properties": { + "id": { "type": "string", "required": true, "minLength": 1 }, + "type": { "type": "string", "enum": [ "boolean", "string" ], "required": true }, + "value": { "type": [ "boolean", "string" ], "required": true } + } + } + } + } + } + } +} diff --git a/xbmc/interfaces/json-rpc/schema/notifications.json b/xbmc/interfaces/json-rpc/schema/notifications.json new file mode 100644 index 0000000..fa738cf --- /dev/null +++ b/xbmc/interfaces/json-rpc/schema/notifications.json @@ -0,0 +1,464 @@ +{ + "Player.OnPlay": { + "type": "notification", + "description": "Playback of a media item has been started or the playback speed has changed. If there is no ID available extra information will be provided.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "$ref": "Player.Notifications.Data", "required": true } + ], + "returns": null + }, + "Player.OnResume": { + "type": "notification", + "description": "Playback of a media item has been resumed. If there is no ID available extra information will be provided.", + "params": [ + { + "name": "sender", + "type": "string", + "required": true + }, + { + "name": "data", + "$ref": "Player.Notifications.Data", + "required": true + } + ], + "returns": null + }, + "Player.OnAVStart": { + "type": "notification", + "description": "Playback of a media item has been started and first frame is available. If there is no ID available extra information will be provided.", + "params": [ + { + "name": "sender", + "type": "string", + "required": true + }, + { + "name": "data", + "$ref": "Player.Notifications.Data", + "required": true + } + ], + "returns": null + }, + "Player.OnAVChange": { + "type": "notification", + "description": "Audio- or videostream has changed. If there is no ID available extra information will be provided.", + "params": [ + { + "name": "sender", + "type": "string", + "required": true + }, + { + "name": "data", + "$ref": "Player.Notifications.Data", + "required": true + } + ], + "returns": null + }, + "Player.OnPause": { + "type": "notification", + "description": "Playback of a media item has been paused. If there is no ID available extra information will be provided.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "$ref": "Player.Notifications.Data", "required": true } + ], + "returns": null + }, + "Player.OnStop": { + "type": "notification", + "description": "Playback of a media item has been stopped. If there is no ID available extra information will be provided.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "item": { "$ref": "Notifications.Item" }, + "end": { "type": "boolean", "required": true, "description": "Whether the player has reached the end of the playable item(s) or not" } + } + } + ], + "returns": null + }, + "Player.OnSpeedChanged": { + "type": "notification", + "description": "Speed of the playback of a media item has been changed. If there is no ID available extra information will be provided.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "$ref": "Player.Notifications.Data", "required": true } + ], + "returns": null + }, + "Player.OnSeek": { + "type": "notification", + "description": "The playback position has been changed. If there is no ID available extra information will be provided.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "item": { "$ref": "Notifications.Item" }, + "player": { "$ref": "Player.Notifications.Player.Seek", "required": true } + } + } + ], + "returns": null + }, + "Player.OnPropertyChanged": { + "type": "notification", + "description": "A property of the playing items has changed.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "property": { "$ref": "Player.Property.Value" }, + "player": { "$ref": "Player.Notifications.Player", "required": true } + } + } + ], + "returns": null + }, + "Playlist.OnAdd": { + "type": "notification", + "description": "A playlist item has been added.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "playlistid": { "$ref": "Playlist.Id", "required": true }, + "item": { "$ref": "Notifications.Item" }, + "position": { "$ref": "Playlist.Position" } + } + } + ], + "returns": null + }, + "Playlist.OnRemove": { + "type": "notification", + "description": "A playlist item has been removed.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "playlistid": { "$ref": "Playlist.Id", "required": true }, + "position": { "$ref": "Playlist.Position" } + } + } + ], + "returns": null + }, + "Playlist.OnClear": { + "type": "notification", + "description": "A playlist item has been cleared.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "playlistid": { "$ref": "Playlist.Id", "required": true } + } + } + ], + "returns": null + }, + "AudioLibrary.OnUpdate": { + "type": "notification", + "description": "An audio item has been updated.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "id": { "$ref": "Library.Id", "required": true }, + "type": { "type": "string", "id": "Notifications.Library.Audio.Type", "enum": [ "song" ], "required": true }, + "transaction": { "$ref": "Optional.Boolean", "description": "True if the update is being performed within a transaction." }, + "added": { "$ref": "Optional.Boolean", "description": "True if the update is for a newly added item." } + } + } + ], + "returns": null + }, + "AudioLibrary.OnRemove": { + "type": "notification", + "description": "An audio item has been removed.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "id": { "$ref": "Library.Id", "required": true }, + "type": { "$ref": "Notifications.Library.Audio.Type", "required": true }, + "transaction": { "$ref": "Optional.Boolean", "description": "True if the removal is being performed within a transaction." } + } + } + ], + "returns": null + }, + "AudioLibrary.OnScanStarted": { + "type": "notification", + "description": "An audio library scan has started.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "AudioLibrary.OnScanFinished": { + "type": "notification", + "description": "Scanning the audio library has been finished.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "AudioLibrary.OnCleanStarted": { + "type": "notification", + "description": "An audio library clean operation has started.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "AudioLibrary.OnCleanFinished": { + "type": "notification", + "description": "The audio library has been cleaned.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "AudioLibrary.OnExport": { + "type": "notification", + "description": "An audio library export has finished.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": false, + "properties": { + "file": { "type": "string", "required": false, "default": "" }, + "failcount": { "type": "integer", "minimum": 0, "required": false, "default": 0 } + } + } + ], + "returns": null + }, + "VideoLibrary.OnUpdate": { + "type": "notification", + "description": "A video item has been updated.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "id": { "$ref": "Library.Id", "required": true }, + "type": { "type": "string", "id": "Notifications.Library.Video.Type", "enum": [ "movie", "tvshow", "episode", "musicvideo" ], "required": true }, + "playcount": { "type": "integer", "minimum": 0, "default": -1 }, + "transaction": { "$ref": "Optional.Boolean", "description": "True if the update is being performed within a transaction." }, + "added": { "$ref": "Optional.Boolean", "description": "True if the update is for a newly added item." } + } + } + ], + "returns": null + }, + "VideoLibrary.OnExport": { + "type": "notification", + "description": "A video library export has finished.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": false, + "properties": { + "file": { "type": "string", "required": false, "default": "" }, + "root": { "type": "string", "required": false, "default": "" }, + "failcount": { "type": "integer", "minimum": 0, "required": false, "default": 0 } + } + } + ], + "returns": null + }, + "VideoLibrary.OnRemove": { + "type": "notification", + "description": "A video item has been removed.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "id": { "$ref": "Library.Id", "required": true }, + "type": { "$ref": "Notifications.Library.Video.Type", "required": true }, + "transaction": { "$ref": "Optional.Boolean", "description": "True if the removal is being performed within a transaction." } + } + } + ], + "returns": null + }, + "VideoLibrary.OnScanStarted": { + "type": "notification", + "description": "A video library scan has started.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "VideoLibrary.OnScanFinished": { + "type": "notification", + "description": "Scanning the video library has been finished.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "VideoLibrary.OnCleanStarted": { + "type": "notification", + "description": "A video library clean operation has started.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "VideoLibrary.OnCleanFinished": { + "type": "notification", + "description": "The video library has been cleaned.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "VideoLibrary.OnRefresh": { + "type": "notification", + "description": "The video library has been refreshed and a home screen reload might be necessary.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "System.OnQuit": { + "type": "notification", + "description": "Kodi will be closed.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "exitcode": { "type": "integer", "minimum": 0, "required": true } + } + } + ], + "returns": null + }, + "System.OnRestart": { + "type": "notification", + "description": "The system will be restarted.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "System.OnSleep": { + "type": "notification", + "description": "The system will be suspended.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "System.OnWake": { + "type": "notification", + "description": "The system woke up from suspension.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "System.OnLowBattery": { + "type": "notification", + "description": "The system is on low battery.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "Application.OnVolumeChanged": { + "type": "notification", + "description": "The volume of the application has changed.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "volume": { "type": "integer", "minimum": 0, "maximum": 100, "required": true }, + "muted": { "type": "boolean", "required": true } + } + } + ], + "returns": null + }, + "Input.OnInputRequested": { + "type": "notification", + "description": "The user is requested to provide some information.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "type": { "type": "string", "enum": [ "keyboard", "time", "date", "ip", "password", "numericpassword", "number", "seconds" ], "required": true }, + "value": { "type": "string", "required": true }, + "title": { "type": "string" } + } + } + ], + "returns": null + }, + "Input.OnInputFinished": { + "type": "notification", + "description": "The user has provided the requested input.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "GUI.OnScreensaverActivated": { + "type": "notification", + "description": "The screensaver has been activated.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "GUI.OnScreensaverDeactivated": { + "type": "notification", + "description": "The screensaver has been deactivated.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "shuttingdown": { "type": "boolean", "required": true } + } + } + ], + "returns": null + }, + "GUI.OnDPMSActivated": { + "type": "notification", + "description": "Energy saving/DPMS has been activated.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "GUI.OnDPMSDeactivated": { + "type": "notification", + "description": "Energy saving/DPMS has been deactivated.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + } +} diff --git a/xbmc/interfaces/json-rpc/schema/types.json b/xbmc/interfaces/json-rpc/schema/types.json new file mode 100644 index 0000000..37b5140 --- /dev/null +++ b/xbmc/interfaces/json-rpc/schema/types.json @@ -0,0 +1,2081 @@ +{ + "Optional.Boolean": { + "type": [ "null", "boolean" ], + "default": null + }, + "Optional.String": { + "type": [ "null", "string" ], + "default": null + }, + "Optional.Integer": { + "type": [ "null", "integer" ], + "default": null + }, + "Optional.Number": { + "type": [ "null", "number" ], + "default": null + }, + "Array.String": { + "type": "array", + "items": { "type": "string", "minLength": 1 } + }, + "Array.Integer": { + "type": "array", + "items": { "type": "integer" } + }, + "Global.Time": { + "type": "object", + "description": "A duration.", + "properties": { + "hours": { "type": "integer", "required": true, "minimum": 0 }, + "minutes": { "type": "integer", "required": true, "minimum": 0, "maximum": 59 }, + "seconds": { "type": "integer", "required": true, "minimum": 0, "maximum": 59 }, + "milliseconds": { "type": "integer", "required": true, "minimum": 0, "maximum": 999 } + }, + "additionalProperties": false + }, + "Global.Weekday": { + "type": "string", + "enum": [ "monday", "tuesday", "wednesday", "thursday", + "friday", "saturday", "sunday" ] + }, + "Global.IncrementDecrement": { + "type": "string", + "enum": [ "increment", "decrement" ] + }, + "Global.Toggle": { + "type": [ + { "type": "boolean", "required": true }, + { "type": "string", "enum": [ "toggle" ], "required": true } + ] + }, + "Global.String.NotEmpty": { + "type": "string", + "minLength": 1 + }, + "Configuration.Notifications": { + "type": "object", + "properties": { + "Player": { "type": "boolean", "required": true }, + "Playlist": { "type": "boolean", "required": true }, + "GUI": { "type": "boolean", "required": true }, + "System": { "type": "boolean", "required": true }, + "VideoLibrary": { "type": "boolean", "required": true }, + "AudioLibrary": { "type": "boolean", "required": true }, + "Application": { "type": "boolean", "required": true }, + "Input": { "type": "boolean", "required": true }, + "PVR": { "type": "boolean", "required": true }, + "Other": { "type": "boolean", "required": true } + }, + "additionalProperties": false + }, + "Configuration": { + "type": "object", "required": true, + "properties": { + "notifications": { "$ref": "Configuration.Notifications", "required": true } + } + }, + "Files.Media": { + "type": "string", + "enum": [ "video", "music", "pictures", "files", "programs" ] + }, + "List.Amount": { + "type": "integer", + "default": -1, + "minimum": 0 + }, + "List.Limits": { + "type": "object", + "properties": { + "start": { "type": "integer", "minimum": 0, "default": 0, "description": "Index of the first item to return" }, + "end": { "$ref": "List.Amount", "description": "Index of the last item to return" } + }, + "additionalProperties": false + }, + "List.LimitsReturned": { + "type": "object", + "properties": { + "start": { "type": "integer", "minimum": 0, "default": 0 }, + "end": { "$ref": "List.Amount" }, + "total": { "type": "integer", "minimum": 0, "required": true } + }, + "additionalProperties": false + }, + "List.Sort": { + "type": "object", + "properties": { + "method": { "type": "string", "default": "none", + "enum": [ "none", "label", "date", "size", "file", "path", "drivetype", "title", "track", "time", "artist", + "album", "albumtype", "genre", "country", "year", "rating", "userrating", "votes", "top250", "programcount", + "playlist", "episode", "season", "totalepisodes", "watchedepisodes", "tvshowstatus", "tvshowtitle", + "sorttitle", "productioncode", "mpaa", "studio", "dateadded", "lastplayed", "playcount", "listeners", + "bitrate", "random", "totaldiscs", "originaldate", "bpm", "originaltitle" ] + }, + "order": { "type": "string", "default": "ascending", "enum": [ "ascending", "descending" ] }, + "ignorearticle": { "type": "boolean", "default": false }, + "useartistsortname": { "type": "boolean", "default": false } + } + }, + "Library.Id": { + "type": "integer", + "default": -1, + "minimum": 1 + }, + "PVR.Channel.Type": { + "type": "string", + "enum": [ "tv", "radio" ] + }, + "Playlist.Id": { + "type": "integer", + "minimum": 0, + "maximum": 2, + "default": -1 + }, + "Playlist.Type": { + "type": "string", + "enum": [ "unknown", "video", "audio", "picture", "mixed" ] + }, + "Playlist.Property.Name": { + "type": "string", + "enum": [ "type", "size" ] + }, + "Playlist.Property.Value": { + "type": "object", + "properties": { + "type": { "$ref": "Playlist.Type" }, + "size": { "type": "integer", "minimum": 0 } + } + }, + "Playlist.Position": { + "type": "integer", + "minimum": 0, + "default": -1 + }, + "Playlist.Item": { + "type": [ + { "type": "object", "properties": { "file": { "type": "string", "description": "Path to a file (not a directory) to be added to the playlist", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "directory": { "type": "string", "required": true }, "recursive": { "type": "boolean", "default": false }, "media": { "$ref": "Files.Media", "default": "files" } }, "additionalProperties": false }, + { "type": "object", "properties": { "movieid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "episodeid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "musicvideoid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "artistid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "albumid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "songid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "genreid": { "$ref": "Library.Id", "required": true, "description": "Identification of a genre from the AudioLibrary" } }, "additionalProperties": false }, + { "type": "object", "properties": { "recordingid": { "$ref": "Library.Id", "required": true, "description": "Identification of a PVR recording" } }, "additionalProperties": false } + ] + }, + "Player.Id": { + "type": "integer", + "minimum": 0, + "maximum": 2, + "default": -1 + }, + "Player.Type": { + "type": "string", + "enum": [ "video", "audio", "picture" ] + }, + "Player.Position.Percentage": { + "type": "number", + "minimum": 0.0, + "maximum": 100.0 + }, + "Player.Position.Time": { + "type": "object", + "description": "A position in duration.", + "additionalProperties": false, + "properties": { + "hours": { "type": "integer", "minimum": 0, "default": 0 }, + "minutes": { "type": "integer", "minimum": 0, "maximum": 59, "default": 0 }, + "seconds": { "type": "integer", "minimum": 0, "maximum": 59, "default": 0 }, + "milliseconds": { "type": "integer", "minimum": 0, "maximum": 999, "default": 0 } + } + }, + "Player.Speed": { + "type": "object", + "required": true, + "properties": { + "speed": { "type": "integer" } + } + }, + "Player.ViewMode": { + "type": "string", + "enum": [ "normal", "zoom", "stretch4x3", "widezoom", "stretch16x9", "original", + "stretch16x9nonlin", "zoom120width", "zoom110width" ] + }, + "Player.CustomViewMode": { + "type": "object", + "required": true, + "properties": { + "zoom": { "type": [ + { "type": "string", "enum": [ "increase", "decrease" ], "required": true }, + { "$ref": "Optional.Number", "minimum":0.5, "maximum": 2.0, "description": "Zoom where 1.0 means 100%", "required": true } ] }, + "pixelratio": { "type": [ + { "type": "string", "enum": [ "increase", "decrease" ], "required": true }, + { "$ref": "Optional.Number", "minimum":0.5, "maximum": 2.0, "description": "Pixel aspect ratio where 1.0 means square pixel", "required": true } ] }, + "verticalshift": { "type": [ + { "type": "string", "enum": [ "increase", "decrease" ], "required": true }, + { "$ref": "Optional.Number", "minimum": -2.0, "maximum": 2.0, "description": "Vertical shift 1.0 means shift to bottom", "required": true } ] }, + "nonlinearstretch": { "type": [ + { "type": "string", "enum": [ "increase", "decrease" ], "required": true }, + { "$ref": "Optional.Boolean", "description": "Flag to enable nonlinear stretch", "required": true } ] } + } + }, + "Player.Repeat": { + "type": "string", + "enum": [ "off", "one", "all" ] + }, + "Player.Audio.Stream": { + "type": "object", + "properties": { + "index": { "type": "integer", "minimum": 0, "required": true }, + "name": { "type": "string", "required": true }, + "language": { "type": "string", "required": true }, + "codec": { "type": "string", "required": true }, + "bitrate": { "type": "integer", "required": true }, + "channels": { "type": "integer", "required": true }, + "isdefault": { "type": "boolean", "required": true }, + "isoriginal": { "type": "boolean", "required": true }, + "isimpaired": { "type": "boolean", "required": true }, + "samplerate": { "type": "integer", "required": true } + } + }, + "Player.Video.Stream": { + "type": "object", + "properties": { + "index": { "type": "integer", "minimum": 0, "required": true }, + "name": { "type": "string", "required": true }, + "language": { "type": "string", "required": true }, + "codec": { "type": "string", "required": true }, + "width": { "type": "integer", "required": true }, + "height": { "type": "integer", "required": true } + } + }, + "Player.Subtitle": { + "type": "object", + "properties": { + "index": { "type": "integer", "minimum": 0, "required": true }, + "name": { "type": "string", "required": true }, + "language": { "type": "string", "required": true }, + "isdefault": { "type": "boolean", "required": true }, + "isforced": { "type": "boolean", "required": true }, + "isimpaired": { "type": "boolean", "required": true } + } + }, + "Player.Property.Name": { + "type": "string", + "enum": [ "type", "partymode", "speed", "time", "percentage", + "totaltime", "playlistid", "position", "repeat", "shuffled", + "canseek", "canchangespeed", "canmove", "canzoom", "canrotate", + "canshuffle", "canrepeat", "currentaudiostream", "audiostreams", + "subtitleenabled", "currentsubtitle", "subtitles", "live", + "currentvideostream", "videostreams", "cachepercentage" ] + }, + "Player.Property.Value": { + "type": "object", + "properties": { + "type": { "$ref": "Player.Type" }, + "partymode": { "type": "boolean" }, + "speed": { "type": "integer" }, + "time": { "$ref": "Global.Time" }, + "percentage": { "$ref": "Player.Position.Percentage" }, + "totaltime": { "$ref": "Global.Time" }, + "playlistid": { "$ref": "Playlist.Id" }, + "position": { "$ref": "Playlist.Position" }, + "repeat": { "$ref": "Player.Repeat" }, + "shuffled": { "type": "boolean" }, + "canseek": { "type": "boolean" }, + "canchangespeed": { "type": "boolean" }, + "canmove": { "type": "boolean" }, + "canzoom": { "type": "boolean" }, + "canrotate": { "type": "boolean" }, + "canshuffle": { "type": "boolean" }, + "canrepeat": { "type": "boolean" }, + "currentaudiostream": { "$ref": "Player.Audio.Stream" }, + "audiostreams": { "type": "array", "items": { "$ref": "Player.Audio.Stream" } }, + "currentvideostream": { "$ref": "Player.Video.Stream" }, + "videostreams": { "type": "array", "items": { "$ref": "Player.Video.Stream" } }, + "subtitleenabled": { "type": "boolean" }, + "currentsubtitle": { "$ref": "Player.Subtitle" }, + "subtitles": { "type": "array", "items": { "$ref": "Player.Subtitle" } }, + "live": { "type": "boolean" }, + "cachepercentage": { "$ref": "Player.Position.Percentage" } + } + }, + "Notifications.Item.Type": { + "type": "string", + "enum": [ "unknown", "movie", "episode", "musicvideo", "song", "picture", "channel" ] + }, + "Notifications.Item": { + "type": [ + { "type": "object", "description": "An unknown item does not have any additional information.", + "properties": { + "type": { "$ref": "Notifications.Item.Type", "required": true } + } + }, + { "type": "object", "description": "An item known to the database has an identification.", + "properties": { + "type": { "$ref": "Notifications.Item.Type", "required": true }, + "id": { "$ref": "Library.Id", "required": true } + } + }, + { "type": "object", "description": "A movie item has a title and may have a release year.", + "properties": { + "type": { "$ref": "Notifications.Item.Type", "required": true }, + "title": { "type": "string", "required": true }, + "year": { "type": "integer" } + } + }, + { "type": "object", "description": "A tv episode has a title and may have an episode number, season number and the title of the show it belongs to.", + "properties": { + "type": { "$ref": "Notifications.Item.Type", "required": true }, + "title": { "type": "string", "required": true }, + "episode": { "type": "integer" }, + "season": { "type": "integer" }, + "showtitle": { "type": "string" } + } + }, + { "type": "object", "description": "A music video has a title and may have an album and an artist.", + "properties": { + "type": { "$ref": "Notifications.Item.Type", "required": true }, + "title": { "type": "string", "required": true }, + "album": { "type": "string" }, + "artist": { "type": "string" } + } + }, + { "type": "object", "description": "A song has a title and may have an album, an artist and a track number.", + "properties": { + "type": { "$ref": "Notifications.Item.Type", "required": true }, + "title": { "type": "string", "required": true }, + "album": { "type": "string" }, + "artist": { "type": "string" }, + "track": { "type": "integer" } + } + }, + { "type": "object", "description": "A picture has a file path.", + "properties": { + "type": { "$ref": "Notifications.Item.Type", "required": true }, + "file": { "type": "string", "required": true } + } + }, + { "type": "object", "description": "A PVR channel is either a radio or tv channel and has a title.", + "properties": { + "type": { "$ref": "Notifications.Item.Type", "required": true }, + "id": { "$ref": "Library.Id", "required": true }, + "title": { "type": "string", "required": true }, + "channeltype": { "$ref": "PVR.Channel.Type", "required": true } + } + } + ] + }, + "Player.Notifications.Player": { + "type": "object", + "properties": { + "playerid": { "$ref": "Player.Id", "required": true }, + "speed": { "type": "integer" } + } + }, + "Player.Notifications.Player.Seek": { + "extends": "Player.Notifications.Player", + "properties": { + "time": { "$ref": "Global.Time" }, + "seekoffset": { "$ref": "Global.Time" } + } + }, + "Player.Notifications.Data": { + "type": "object", + "properties": { + "item": { "$ref": "Notifications.Item", "required": true }, + "player": { "$ref": "Player.Notifications.Player", "required": true } + } + }, + "Item.Fields.Base": { + "type": "array", + "uniqueItems": true, + "items": { "type": "string" } + }, + "Item.Details.Base": { + "type": "object", + "properties": { + "label": { "type": "string", "required": true } + } + }, + "Item.CustomProperties": { + "type": "object", + "additionalProperties": { "$ref": "Global.String.NotEmpty" } + }, + "Media.Details.Base": { + "extends": "Item.Details.Base", + "properties": { + "fanart": { "type": "string" }, + "thumbnail": { "type": "string" } + } + }, + "Media.Artwork": { + "type": "object", + "properties": { + "thumb": { "$ref": "Global.String.NotEmpty" }, + "poster": { "$ref": "Global.String.NotEmpty" }, + "banner": { "$ref": "Global.String.NotEmpty" }, + "fanart": { "$ref": "Global.String.NotEmpty" } + }, + "additionalProperties": { "$ref": "Global.String.NotEmpty" } + }, + "Media.Artwork.Set": { + "type": "object", + "properties": { + "thumb": { "type": [ "null", { "$ref": "Global.String.NotEmpty", "required": true } ], "default": "" }, + "poster": { "type": [ "null", { "$ref": "Global.String.NotEmpty", "required": true } ], "default": "" }, + "banner": { "type": [ "null", { "$ref": "Global.String.NotEmpty", "required": true } ], "default": "" }, + "fanart": { "type": [ "null", { "$ref": "Global.String.NotEmpty", "required": true } ], "default": "" } + }, + "additionalProperties": { "type": [ "null", { "$ref": "Global.String.NotEmpty", "required": true } ] } + }, + "Video.Rating": { + "type": "object", + "properties": { + "rating": { "type": "number", "required": true }, + "votes": { "type": "integer" }, + "default": { "type": "boolean" } + } + }, + "Video.Ratings": { + "type": "object", + "additionalProperties": { "$ref": "Video.Rating" } + }, + "Video.Ratings.Set": { + "type": "object", + "additionalProperties": { "type": [ "null", { "$ref": "Video.Rating", "required": true } ] } + }, + "Media.UniqueID": { + "type": "object", + "additionalProperties": { "$ref": "Global.String.NotEmpty" } + }, + "Media.UniqueID.Set": { + "type": "object", + "additionalProperties": { "type": [ "null", { "$ref": "Global.String.NotEmpty", "required": true } ] } + }, + "Library.Fields.Source": { + "extends": "Item.Fields.Base", + "items": { "type": "string", "enum": [ "file", "paths" ] } + }, + "Library.Details.Source": { + "extends": "Item.Details.Base", + "properties": { + "sourceid": { "$ref": "Library.Id", "required": true }, + "file": { "type": "string", "description": "The url encoded multipath string combining all paths of the source ", "required": true }, + "paths": { "$ref": "Array.String", "description": "The individual paths of the media source" } + } + }, + "Library.Fields.Genre": { + "extends": "Item.Fields.Base", + "items": { "type": "string", "enum": [ "title", "thumbnail", "sourceid" ] } + }, + "Library.Details.Genre": { + "extends": "Item.Details.Base", + "properties": { + "genreid": { "$ref": "Library.Id", "required": true }, + "title": { "type": "string" }, + "thumbnail": { "type": "string" }, + "sourceid": { "$ref": "Array.Integer", "description": "The ids of sources with songs of the genre" } + } + }, + "Library.Fields.Tag": { + "extends": "Item.Fields.Base", + "items": { "type": "string", "enum": [ "title" ] } + }, + "Library.Details.Tag": { + "extends": "Item.Details.Base", + "properties": { + "tagid": { "$ref": "Library.Id", "required": true }, + "title": { "type": "string" } + } + }, + "Audio.Fields.Role": { + "extends": "Item.Fields.Base", + "items": { "type": "string", "enum": [ "title" ] } + }, + "Audio.Details.Role": { + "extends": "Item.Details.Base", + "properties": { + "roleid": { "$ref": "Library.Id", "required": true }, + "title": { "type": "string" } + } + }, + "Audio.Fields.Artist": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "description": "Requesting the (song)genreid/genre, roleid/role or sourceid fields will result in increased response times", + "enum": [ "instrument", "style", "mood", "born", "formed", + "description", "genre", "died", "disbanded", + "yearsactive", "musicbrainzartistid", "fanart", + "thumbnail", "compilationartist", "dateadded", + "roles", "songgenres", "isalbumartist", + "sortname", "type", "gender", "disambiguation", "art", "sourceid", + "datemodified", "datenew" ] + } + }, + "Audio.Fields.Album": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "description": "Requesting the songgenres, artistid and/or sourceid fields will result in increased response times", + "enum": [ "title", "description", "artist", "genre", + "theme", "mood", "style", "type", "albumlabel", + "rating", "votes", "userrating","year", "musicbrainzalbumid", + "musicbrainzalbumartistid", "fanart", "thumbnail", + "playcount", "artistid", "displayartist", + "compilation", "releasetype", "dateadded", + "sortartist", "musicbrainzreleasegroupid", "songgenres", "art", + "lastplayed", "sourceid","isboxset", "totaldiscs", + "releasedate", "originaldate", "albumstatus", "datemodified", "datenew", + "albumduration"] + } + }, + "Audio.Fields.Song": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "description": "Requesting the genreid, artistid, albumartistid and/or sourceid fields will result in increased response times", + "enum": [ "title", "artist", "albumartist", "genre", "year", + "rating", "album", "track", "duration", "comment", + "lyrics", "musicbrainztrackid", "musicbrainzartistid", + "musicbrainzalbumid", "musicbrainzalbumartistid", + "playcount", "fanart", "thumbnail", "file", "albumid", + "lastplayed", "disc", "genreid", "artistid", "displayartist", + "albumartistid", "albumreleasetype", "dateadded", + "votes", "userrating", "mood", "contributors", + "displaycomposer", "displayconductor", "displayorchestra", "displaylyricist", + "sortartist", "art", "sourceid", "disctitle", "releasedate", "originaldate", + "bpm", "samplerate", "bitrate", "channels", "datemodified", "datenew" ] + } + }, + "Audio.Album.ReleaseType": { + "type": "string", + "enum": [ "album", "single" ], + "default": "album" + }, + "Audio.Contributors": { + "type": "array", + "items": { "type": "object", + "description": "The artist and the role they contribute to a song", + "properties": { + "name": { "type": "string", "required": true }, + "role": { "type": "string", "required": true }, + "roleid": { "$ref": "Library.Id", "required": true }, + "artistid": { "$ref": "Library.Id", "required": true } + }, + "additionalProperties": false + } + }, + "Audio.Artist.Roles": { + "type": "array", + "items": { "type": "object", + "description": "The various roles contributed by an artist to one or more songs", + "properties": { + "roleid": { "$ref": "Library.Id", "required": true }, + "role": { "type": "string", "required": true } + }, + "additionalProperties": false + } + }, + "Audio.Details.Genres": { + "type": "array", + "items": { "type": "object", + "properties": { + "genreid": { "$ref": "Library.Id", "required": true }, + "title": { "type": "string" } + } + } + }, + "Audio.Details.Base": { + "extends": "Media.Details.Base", + "properties": { + "genre": { "$ref": "Array.String" }, + "dateadded": { "type": "string" }, + "art": { "$ref": "Media.Artwork" } + } + }, + "Audio.Details.Media": { + "extends": "Audio.Details.Base", + "properties": { + "title": { "type": "string" }, + "artist": { "$ref": "Array.String" }, + "year": { "type": "integer" }, + "rating": { "type": "number" }, + "musicbrainzalbumartistid": { "$ref": "Array.String" }, + "artistid": { "$ref": "Array.Integer" }, + "displayartist": { "type" : "string" }, + "votes": { "type": "integer" }, + "userrating": { "type": "integer" }, + "sortartist": { "type" : "string" }, + "releasedate": { "type" : "string" }, + "originaldate": { "type" : "string" } + } + }, + "Audio.Details.Artist": { + "extends": "Audio.Details.Base", + "properties": { + "artistid": { "$ref": "Library.Id", "required": true }, + "artist": { "type": "string", "required": true }, + "instrument": { "$ref": "Array.String" }, + "style": { "$ref": "Array.String" }, + "mood": { "$ref": "Array.String" }, + "born": { "type": "string" }, + "formed": { "type": "string" }, + "description": { "type": "string" }, + "died": { "type": "string" }, + "disbanded": { "type": "string" }, + "yearsactive": { "$ref": "Array.String" }, + "compilationartist": { "type": "boolean" }, + "musicbrainzartistid": { "$ref": "Array.String" }, + "roles": {"$ref": "Audio.Artist.Roles"}, + "songgenres": {"$ref": "Audio.Details.Genres"}, + "isalbumartist": { "type": "boolean" }, + "sortname": { "type": "string" }, + "type": { "type": "string" }, + "gender": { "type": "string" }, + "disambiguation": { "type": "string" }, + "sourceid": { "$ref": "Array.Integer" } + } + }, + "Audio.Details.Album": { + "extends": "Audio.Details.Media", + "properties": { + "albumid": { "$ref": "Library.Id", "required": true }, + "description": { "type": "string" }, + "theme": { "$ref": "Array.String" }, + "mood": { "$ref": "Array.String" }, + "style": { "$ref": "Array.String" }, + "type": { "type": "string" }, + "albumlabel": { "type": "string" }, + "playcount": { "type": "integer" }, + "compilation": { "type": "boolean" }, + "releasetype": { "$ref": "Audio.Album.ReleaseType" }, + "musicbrainzreleasegroupid": { "type": "string" }, + "musicbrainzalbumid": { "type": "string" }, + "songgenres": {"$ref": "Audio.Details.Genres"}, + "lastplayed": { "type": "string" }, + "sourceid": { "$ref": "Array.Integer" }, + "isboxset" : { "type": "boolean" }, + "totaldiscs": { "type": "integer" }, + "albumstatus": { "type": "string" }, + "albumduration": { "type": "integer" } + } + }, + "Audio.Details.Song": { + "extends": "Audio.Details.Media", + "properties": { + "songid": { "$ref": "Library.Id", "required": true }, + "file": { "type": "string" }, + "albumartist": { "$ref": "Array.String" }, + "album": { "type": "string" }, + "track": { "type": "integer" }, + "duration": { "type": "integer" }, + "comment": { "type": "string" }, + "lyrics": { "type": "string" }, + "playcount": { "type": "integer" }, + "musicbrainztrackid": { "type": "string" }, + "musicbrainzartistid": { "$ref": "Array.String" }, + "albumid": { "$ref": "Library.Id" }, + "lastplayed": { "type": "string" }, + "disc": { "type": "integer" }, + "albumartistid": { "$ref": "Array.Integer" }, + "albumreleasetype": { "$ref": "Audio.Album.ReleaseType" }, + "mood": { "type": "string"}, + "contributors": { "$ref": "Audio.Contributors" }, + "displaycomposer": { "type": "string"}, + "displayconductor": { "type": "string"}, + "displayorchestra": { "type": "string"}, + "displaylyricist": { "type": "string"}, + "genreid": { "$ref": "Array.Integer"}, + "sourceid": { "$ref": "Array.Integer" }, + "disctitle": { "type": "string" }, + "bpm": { "type": "Integer" }, + "samplerate": { "type": "Integer" }, + "bitrate": { "type": "Integer"}, + "channels": { "type": "Integer"} + } + }, + "Audio.Property.Name": { + "type": "string", + "enum": [ "missingartistid", "librarylastupdated", "librarylastcleaned", "artistlinksupdated", + "songslastadded", "albumslastadded", "artistslastadded", "genreslastadded", + "songsmodified", "albumsmodified", "artistsmodified"] + }, + "Audio.Property.Value": { + "type": "object", + "properties": { + "missingartistid": { "$ref": "Library.Id" }, + "librarylastupdated": { "type": "string" }, + "librarylastcleaned": { "type": "string" }, + "artistlinksupdated": { "type": "string" }, + "songslastadded": { "type": "string" }, + "albumslastadded": { "type": "string" }, + "artistslastadded": { "type": "string" }, + "genreslastadded": { "type": "string" }, + "songsmodified": { "type": "string" }, + "albumsmodified": { "type": "string" }, + "artistsmodified": { "type": "string" } + } + }, + "Video.Fields.Movie": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "description": "Requesting the cast, ratings, showlink, streamdetails, uniqueid and/or tag field will result in increased response times", + "enum": [ "title", "genre", "year", "rating", "director", "trailer", + "tagline", "plot", "plotoutline", "originaltitle", "lastplayed", + "playcount", "writer", "studio", "mpaa", "cast", "country", + "imdbnumber", "runtime", "set", "showlink", "streamdetails", + "top250", "votes", "fanart", "thumbnail", "file", "sorttitle", + "resume", "setid", "dateadded", "tag", "art", "userrating", + "ratings", "premiered", "uniqueid" ] + } + }, + "Video.Fields.MovieSet": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "title", "playcount", "fanart", "thumbnail", "art", "plot" ] + } + }, + "Video.Fields.TVShow": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "description": "Requesting the cast, ratings, uniqueid and/or tag field will result in increased response times", + "enum": [ "title", "genre", "year", "rating", "plot", + "studio", "mpaa", "cast", "playcount", "episode", + "imdbnumber", "premiered", "votes", "lastplayed", + "fanart", "thumbnail", "file", "originaltitle", + "sorttitle", "episodeguide", "season", "watchedepisodes", + "dateadded", "tag", "art", "userrating", "ratings", + "runtime", "uniqueid" ] + } + }, + "Video.Fields.Season": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "season", "showtitle", "playcount", "episode", "fanart", "thumbnail", "tvshowid", + "watchedepisodes", "art", "userrating", "title" ] + } + }, + "Video.Fields.Episode": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "description": "Requesting the cast, ratings, streamdetails, uniqueid and/or tag field will result in increased response times", + "enum": [ "title", "plot", "votes", "rating", "writer", + "firstaired", "playcount", "runtime", "director", + "productioncode", "season", "episode", "originaltitle", + "showtitle", "cast", "streamdetails", "lastplayed", "fanart", + "thumbnail", "file", "resume", "tvshowid", "dateadded", + "uniqueid", "art", "specialsortseason", "specialsortepisode", "userrating", + "seasonid", "ratings" ] + } + }, + "Video.Fields.MusicVideo": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "description": "Requesting the streamdetails, uniqueid and/or tag field will result in increased response times", + "enum": [ "title", "playcount", "runtime", "director", + "studio", "year", "plot", "album", "artist", + "genre", "track", "streamdetails", "lastplayed", + "fanart", "thumbnail", "file", "resume", "dateadded", + "tag", "art", "rating", "userrating", "premiered", "uniqueid" ] + } + }, + "Video.Cast": { + "type": "array", + "items": { "type": "object", + "properties": { + "name": { "type": "string", "required": true }, + "role": { "type": "string", "required": true }, + "order": { "type": "integer", "required": true }, + "thumbnail": { "type": "string" } + }, + "additionalProperties": false + } + }, + "Video.Streams": { + "type": "object", + "properties": { + "audio": { "type": "array", "minItems": 1, + "items": { "type": "object", + "properties": { + "codec": { "type": "string" }, + "language": { "type": "string" }, + "channels": { "type": "integer" } + }, + "additionalProperties": false + } + }, + "video": { "type": "array", "minItems": 1, + "items": { "type": "object", + "properties": { + "codec": { "type": "string" }, + "aspect": { "type": "number" }, + "width": { "type": "integer" }, + "height": { "type": "integer" }, + "duration": { "type": "integer" }, + "hdrtype": { "type": "string"} + }, + "additionalProperties": false + } + }, + "subtitle": { "type": "array", "minItems": 1, + "items": { "type": "object", + "properties": { + "language": { "type": "string" } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "Video.Resume": { + "type": "object", + "properties": { + "position": { "type": "number", "minimum": 0.0 }, + "total": { "type": "number", "minimum": 0.0 } + }, + "additionalProperties": false + }, + "Video.Details.Base": { + "extends": "Media.Details.Base", + "properties": { + "playcount": { "type": "integer" }, + "art": { "$ref": "Media.Artwork" } + } + }, + "Video.Details.Media": { + "extends": "Video.Details.Base", + "properties": { + "title": { "type": "string" } + } + }, + "Video.Details.Item": { + "extends": "Video.Details.Media", + "properties": { + "file": { "type": "string" }, + "plot": { "type": "string" }, + "lastplayed": { "type": "string" }, + "dateadded": { "type": "string" } + } + }, + "Video.Details.File": { + "extends": "Video.Details.Item", + "properties": { + "runtime": { "type": "integer", "description": "Runtime in seconds" }, + "director": { "$ref": "Array.String" }, + "streamdetails": { "$ref": "Video.Streams" }, + "resume": { "$ref": "Video.Resume" } + } + }, + "Video.Details.Movie": { + "extends": "Video.Details.File", + "properties": { + "movieid": { "$ref": "Library.Id", "required": true }, + "genre": { "$ref": "Array.String" }, + "year": { "type": "integer" }, + "rating": { "type": "number" }, + "trailer": { "type": "string" }, + "tagline": { "type": "string" }, + "plotoutline": { "type": "string" }, + "originaltitle": { "type": "string" }, + "sorttitle": { "type": "string" }, + "writer": { "$ref": "Array.String" }, + "studio": { "$ref": "Array.String" }, + "mpaa": { "type": "string" }, + "cast": { "$ref": "Video.Cast" }, + "country": { "$ref": "Array.String" }, + "imdbnumber": { "type": "string" }, + "set": { "type": "string" }, + "showlink": { "$ref": "Array.String" }, + "top250": { "type": "integer" }, + "votes": { "type": "string" }, + "setid": { "$ref": "Library.Id" }, + "tag": { "$ref": "Array.String" }, + "userrating": { "type": "integer" }, + "ratings": { "type": "Video.Ratings" }, + "premiered": { "type": "string" }, + "uniqueid": { "$ref": "Media.UniqueID" } + } + }, + "Video.Details.MovieSet": { + "extends": "Video.Details.Media", + "properties": { + "setid": { "$ref": "Library.Id", "required": true }, + "plot": { "type": "string" } + } + }, + "Video.Details.MovieSet.Extended": { + "extends": "Video.Details.MovieSet", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "movies": { "type": "array", + "items": { "$ref": "Video.Details.Movie" } + } + } + }, + "Video.Details.TVShow": { + "extends": "Video.Details.Item", + "properties": { + "tvshowid": { "$ref": "Library.Id", "required": true }, + "genre": { "$ref": "Array.String" }, + "year": { "type": "integer" }, + "rating": { "type": "number" }, + "originaltitle": { "type": "string" }, + "sorttitle": { "type": "string" }, + "studio": { "$ref": "Array.String" }, + "mpaa": { "type": "string" }, + "cast": { "$ref": "Video.Cast" }, + "episode": { "type": "integer" }, + "watchedepisodes": { "type": "integer" }, + "imdbnumber": { "type": "string" }, + "premiered": { "type": "string" }, + "votes": { "type": "string" }, + "episodeguide": { "type": "string" }, + "season": { "type": "integer" }, + "tag": { "$ref": "Array.String" }, + "userrating": { "type": "integer" }, + "ratings": { "type": "Video.Ratings" }, + "runtime": { "type": "integer", "description": "Runtime in seconds" }, + "status": { "type": "string", "description": "Returns 'returning series', 'in production', 'planned', 'cancelled' or 'ended'" }, + "uniqueid": { "$ref": "Media.UniqueID" } + } + }, + "Video.Details.Season": { + "extends": "Video.Details.Base", + "properties": { + "seasonid": { "$ref": "Library.Id", "required": true }, + "season": { "type": "integer", "required": true }, + "showtitle": { "type": "string" }, + "episode": { "type": "integer" }, + "watchedepisodes": { "type": "integer" }, + "tvshowid": { "$ref": "Library.Id" }, + "userrating": { "type": "integer" }, + "title": { "type": "string" } + } + }, + "Video.Details.Episode": { + "extends": "Video.Details.File", + "properties": { + "episodeid": { "$ref": "Library.Id", "required": true }, + "votes": { "type": "string" }, + "rating": { "type": "number" }, + "writer": { "$ref": "Array.String" }, + "firstaired": { "type": "string" }, + "productioncode": { "type": "string" }, + "season": { "type": "integer" }, + "episode": { "type": "integer" }, + "uniqueid": { "$ref": "Media.UniqueID" }, + "originaltitle": { "type": "string" }, + "showtitle": { "type": "string" }, + "cast": { "$ref": "Video.Cast" }, + "tvshowid": { "$ref": "Library.Id" }, + "specialsortseason": { "type": "integer" }, + "specialsortepisode": { "type": "integer" }, + "userrating": { "type": "integer" }, + "seasonid": { "$ref": "Library.Id" }, + "ratings": { "type": "Video.Ratings" } + } + }, + "Video.Details.MusicVideo": { + "extends": "Video.Details.File", + "properties": { + "musicvideoid": { "$ref": "Library.Id", "required": true }, + "studio": { "$ref": "Array.String" }, + "year": { "type": "integer" }, + "album": { "type": "string" }, + "artist": { "$ref": "Array.String" }, + "genre": { "$ref": "Array.String" }, + "track": { "type": "integer" }, + "tag": { "$ref": "Array.String" }, + "rating": { "type": "number" }, + "userrating": { "type": "integer" }, + "premiered": { "type": "string" }, + "uniqueid": { "$ref": "Media.UniqueID" } + } + }, + "PVR.Property.Name": { + "type": "string", + "enum": [ "available", "recording", "scanning" ] + }, + "PVR.Property.Value": { + "type": "object", + "properties": { + "available": { "type": "boolean" }, + "recording": { "type": "boolean" }, + "scanning": { "type": "boolean" } + } + }, + "PVR.ChannelGroup.Id": { + "type": [ + { "$ref": "Library.Id", "required": true }, + { "type": "string", "enum": [ "alltv", "allradio" ], "required": true } + ] + }, + "PVR.Fields.Broadcast": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "title", "plot", "plotoutline", "starttime", + "endtime", "runtime", "progress", "progresspercentage", + "genre", "episodename", "episodenum", "episodepart", + "firstaired", "hastimer", "isactive", "parentalrating", + "wasactive", "thumbnail", "rating", "originaltitle", "cast", + "director", "writer", "year", "imdbnumber", "hastimerrule", + "hasrecording", "recording", "isseries", "isplayable", "clientid", + "hasreminder", "seasonnum" ] + } + }, + "PVR.Details.Broadcast": { + "extends": "Item.Details.Base", + "properties": { + "broadcastid": { "$ref": "Library.Id", "required": true }, + "title": { "type": "string" }, + "plot": { "type": "string" }, + "plotoutline": { "type": "string" }, + "starttime": { "type": "string" }, + "endtime": { "type": "string" }, + "runtime": { "type": "integer" }, + "progress": { "type": "integer" }, + "progresspercentage": { "type": "number" }, + "genre": { "type": "string" }, + "episodename": { "type": "string" }, + "episodenum": { "type": "integer" }, + "episodepart": { "type": "integer" }, + "firstaired": { "type": "string" }, + "hastimer": { "type": "boolean" }, + "isactive": { "type": "boolean" }, + "parentalrating": { "type": "integer" }, + "wasactive": { "type": "boolean" }, + "thumbnail": { "type": "string" }, + "rating": { "type": "integer" }, + "originaltitle": { "type": "string" }, + "cast": { "type": "string" }, + "director": { "type": "string" }, + "writer": { "type": "string" }, + "year": { "type": "integer" }, + "imdbnumber": { "type": "integer" }, + "hastimerrule": { "type": "boolean" }, + "hasrecording": { "type": "boolean" }, + "recording": { "type": "string" }, + "isseries": { "type": "boolean" }, + "isplayable": { "type": "boolean", "description": "Deprecated - Use GetBroadcastIsPlayable instead" }, + "clientid": { "$ref": "Library.Id" }, + "hasreminder": { "type": "boolean" }, + "seasonnum": { "type": "integer" } + } + }, + "PVR.Fields.Channel": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "thumbnail", "channeltype", "hidden", "locked", "channel", "lastplayed", + "broadcastnow", "broadcastnext", "uniqueid", "icon", "channelnumber", + "subchannelnumber", "isrecording", "hasarchive", "clientid" ] + } + }, + "PVR.Details.Channel": { + "extends": "Item.Details.Base", + "properties": { + "channelid": { "$ref": "Library.Id", "required": true }, + "channel": { "type": "string" }, + "channeltype": { "$ref": "PVR.Channel.Type" }, + "hidden": { "type": "boolean" }, + "locked": { "type": "boolean" }, + "thumbnail": { "type": "string" }, + "lastplayed": { "type": "string" }, + "broadcastnow": { "$ref": "PVR.Details.Broadcast" }, + "broadcastnext": { "$ref": "PVR.Details.Broadcast" }, + "uniqueid": { "type": "integer", "required": true }, + "icon": { "type": "string" }, + "channelnumber": { "type": "integer" }, + "subchannelnumber": { "type": "integer" }, + "isrecording": { "type": "boolean" }, + "hasarchive": { "type": "boolean" }, + "clientid": { "$ref": "Library.Id" } + } + }, + "PVR.Details.ChannelGroup": { + "extends": "Item.Details.Base", + "properties": { + "channelgroupid": { "$ref": "Library.Id", "required": true }, + "channeltype": { "$ref": "PVR.Channel.Type", "required": true } + } + }, + "PVR.Details.ChannelGroup.Extended": { + "extends": "PVR.Details.ChannelGroup", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "channels": { "type": "array", + "items": { "$ref": "PVR.Details.Channel" } + } + } + }, + "PVR.Fields.Client": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "addonid", "supportstv", "supportsradio", "supportsepg", + "supportsrecordings", "supportstimers", "supportschannelgroups", + "supportschannelscan" ] + } + }, + "PVR.Details.Client": { + "extends": "Item.Details.Base", + "properties": { + "clientid": { "$ref": "Library.Id", "required": true }, + "addonid": { "type": "string" }, + "supportstv": { "type": "boolean" }, + "supportsradio": { "type": "boolean" }, + "supportsepg": { "type": "boolean" }, + "supportsrecordings": { "type": "boolean" }, + "supportstimers": { "type": "boolean" }, + "supportschannelgroups": { "type": "boolean" }, + "supportschannelscan": { "type": "boolean" } + } + }, + "PVR.TimerState": { + "type": "string", + "enum": [ "unknown", "new", "scheduled", "recording", "completed", + "aborted", "cancelled", "conflict_ok", "conflict_notok", + "error", "disabled" ] + }, + "PVR.Fields.Timer": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "title", "summary", "channelid", "isradio", "istimerrule", "ismanual", + "starttime", "endtime", "runtime", "lifetime", "firstday", + "weekdays", "priority", "startmargin", "endmargin", "state", + "file", "directory", "preventduplicateepisodes", "startanytime", + "endanytime", "epgsearchstring", "fulltextepgsearch", "recordinggroup", + "maxrecordings", "epguid", "isreadonly", "isreminder", "clientid", "broadcastid" ] + } + }, + "PVR.Details.Timer": { + "extends": "Item.Details.Base", + "properties": { + "timerid": { "$ref": "Library.Id", "required": true }, + "title": { "type": "string" }, + "summary": { "type": "string" }, + "channelid": { "$ref": "Library.Id" }, + "isradio": { "type": "boolean" }, + "istimerrule": { "type": "boolean" }, + "ismanual": { "type": "boolean" }, + "starttime": { "type": "string" }, + "endtime": { "type": "string" }, + "runtime": { "type": "integer" }, + "lifetime": { "type": "integer" }, + "firstday": { "type": "string" }, + "weekdays": { "type": "array", + "items": { "$ref": "Global.Weekday" }, + "uniqueItems": true + }, + "priority": { "type": "integer" }, + "startmargin": { "type": "integer" }, + "endmargin": { "type": "integer" }, + "state": { "$ref": "PVR.TimerState" }, + "file": { "type": "string" }, + "directory": { "type": "string" }, + "preventduplicateepisodes": { "type": "integer" }, + "startanytime": { "type": "boolean" }, + "endanytime": { "type": "boolean" }, + "epgsearchstring": { "type": "string" }, + "fulltextepgsearch": { "type": "boolean" }, + "recordinggroup": { "type": "integer" }, + "maxrecordings": { "type": "integer" }, + "epguid": { "type": "integer" }, + "isreadonly": { "type": "boolean" }, + "isreminder": { "type": "boolean" }, + "clientid": { "$ref": "Library.Id" }, + "broadcastid": { "$ref": "Library.Id" } + } + }, + "PVR.Fields.Recording": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "title", "plot", "plotoutline", "genre", "playcount", + "resume", "channel", "starttime","endtime", "runtime", + "lifetime", "icon", "art", "streamurl", "file", + "directory", "radio", "isdeleted", "epgeventid", "channeluid", + "season", "episode", "showtitle", "clientid" ] + } + }, + "PVR.Details.Recording": { + "extends": "Item.Details.Base", + "properties": { + "recordingid": { "$ref": "Library.Id", "required": true }, + "title": { "type": "string" }, + "plot": { "type": "string" }, + "plotoutline": { "type": "string" }, + "genre": { "type": "string" }, + "playcount": { "type": "integer" }, + "resume": { "$ref": "Video.Resume" }, + "channel": { "type": "string" }, + "starttime": { "type": "string" }, + "endtime": { "type": "string" }, + "runtime": { "type": "integer" }, + "lifetime": { "type": "integer" }, + "icon": { "type": "string" }, + "art": { "$ref": "Media.Artwork" }, + "streamurl": { "type": "string" }, + "file": { "type": "string" }, + "directory": { "type": "string" }, + "radio": { "type": "boolean" }, + "isdeleted": { "type": "boolean" }, + "epgeventid": { "type": "integer" }, + "channeluid": { "type": "integer" }, + "season": { "type": "integer" }, + "episode": { "type": "integer" }, + "showtitle": { "type": "string" }, + "clientid": { "$ref": "Library.Id" } + } + }, + "Textures.Details.Size": { + "type": "object", + "properties": { + "size": { "type": "integer", "description": "Size of the texture (1 == largest)" }, + "width": { "type": "integer", "description": "Width of texture" }, + "height": { "type": "integer", "description": "Height of texture" }, + "usecount": { "type": "integer", "description": "Number of uses" }, + "lastused": { "type": "string", "description": "Date of last use" } + } + }, + "Textures.Fields.Texture": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "url", "cachedurl", "lasthashcheck", "imagehash", "sizes" ] + } + }, + "Textures.Details.Texture": { + "type": "object", + "properties": { + "textureid": { "$ref": "Library.Id", "required": "true" }, + "url": { "type": "string", "description": "Original source URL" }, + "cachedurl": { "type": "string", "description": "Cached URL on disk" }, + "lasthashcheck": { "type": "string", "description": "Last time source was checked for changes" }, + "imagehash": { "type": "string", "description": "Hash of image" }, + "sizes": { "type": "array", "items": { "$ref": "Textures.Details.Size" } } + } + }, + "Profiles.Password": { + "type": "object", + "properties": { + "value": { "type": "string", "required": true, "description": "Password" }, + "encryption": { "type": "string", "description": "Password Encryption", "default": "md5", "enum": [ "none", "md5" ] } + } + }, + "Profiles.Fields.Profile": { + "extends": "Item.Fields.Base", + "items": { "type": "string", "enum": [ "thumbnail", "lockmode" ] } + }, + "Profiles.Details.Profile": { + "extends": "Item.Details.Base", + "properties": { + "thumbnail": { "type": "string" }, + "lockmode": { "type": "integer" } + } + }, + "List.Filter.Rule": { + "type": "object", + "properties": { + "operator": { "$ref": "List.Filter.Operators", "required": true }, + "value": { + "type": [ + { "type": "string", "required": true }, + { "type": "array", "items": { "type": "string" }, "required": true } + ], "required": true + } + } + }, + "List.Filter.Rule.Movies": { + "extends": "List.Filter.Rule", + "properties": { + "field": { "$ref": "List.Filter.Fields.Movies", "required": true } + } + }, + "List.Filter.Rule.TVShows": { + "extends": "List.Filter.Rule", + "properties": { + "field": { "$ref": "List.Filter.Fields.TVShows", "required": true } + } + }, + "List.Filter.Rule.Episodes": { + "extends": "List.Filter.Rule", + "properties": { + "field": { "$ref": "List.Filter.Fields.Episodes", "required": true } + } + }, + "List.Filter.Rule.MusicVideos": { + "extends": "List.Filter.Rule", + "properties": { + "field": { "$ref": "List.Filter.Fields.MusicVideos", "required": true } + } + }, + "List.Filter.Rule.Artists": { + "extends": "List.Filter.Rule", + "properties": { + "field": { "$ref": "List.Filter.Fields.Artists", "required": true } + } + }, + "List.Filter.Rule.Albums": { + "extends": "List.Filter.Rule", + "properties": { + "field": { "$ref": "List.Filter.Fields.Albums", "required": true } + } + }, + "List.Filter.Rule.Songs": { + "extends": "List.Filter.Rule", + "properties": { + "field": { "$ref": "List.Filter.Fields.Songs", "required": true } + } + }, + "List.Filter.Rule.Textures": { + "extends": "List.Filter.Rule", + "properties": { + "field": { "$ref": "List.Filter.Fields.Textures", "required": true } + } + }, + "List.Filter.Movies": { + "type": [ + { "type": "object", + "properties": { + "and": { "type": "array", + "items": { "$ref": "List.Filter.Movies" }, + "minItems": 1, "required": true + } + } + }, + { "type": "object", + "properties": { + "or": { "type": "array", + "items": { "$ref": "List.Filter.Movies" }, + "minItems": 1, "required": true + } + } + }, + { "$ref": "List.Filter.Rule.Movies" } + ] + }, + "List.Filter.TVShows": { + "type": [ + { "type": "object", + "properties": { + "and": { "type": "array", + "items": { "$ref": "List.Filter.TVShows" }, + "minItems": 1, "required": true + } + } + }, + { "type": "object", + "properties": { + "or": { "type": "array", + "items": { "$ref": "List.Filter.TVShows" }, + "minItems": 1, "required": true + } + } + }, + { "$ref": "List.Filter.Rule.TVShows" } + ] + }, + "List.Filter.Episodes": { + "type": [ + { "type": "object", + "properties": { + "and": { "type": "array", + "items": { "$ref": "List.Filter.Episodes" }, + "minItems": 1, "required": true + } + } + }, + { "type": "object", + "properties": { + "or": { "type": "array", + "items": { "$ref": "List.Filter.Episodes" }, + "minItems": 1, "required": true + } + } + }, + { "$ref": "List.Filter.Rule.Episodes" } + ] + }, + "List.Filter.MusicVideos": { + "type": [ + { "type": "object", + "properties": { + "and": { "type": "array", + "items": { "$ref": "List.Filter.MusicVideos" }, + "minItems": 1, "required": true + } + } + }, + { "type": "object", + "properties": { + "or": { "type": "array", + "items": { "$ref": "List.Filter.MusicVideos" }, + "minItems": 1, "required": true + } + } + }, + { "$ref": "List.Filter.Rule.MusicVideos" } + ] + }, + "List.Filter.Artists": { + "type": [ + { "type": "object", + "properties": { + "and": { "type": "array", + "items": { "$ref": "List.Filter.Artists" }, + "minItems": 1, "required": true + } + } + }, + { "type": "object", + "properties": { + "or": { "type": "array", + "items": { "$ref": "List.Filter.Artists" }, + "minItems": 1, "required": true + } + } + }, + { "$ref": "List.Filter.Rule.Artists" } + ] + }, + "List.Filter.Albums": { + "type": [ + { "type": "object", + "properties": { + "and": { "type": "array", + "items": { "$ref": "List.Filter.Albums" }, + "minItems": 1, "required": true + } + } + }, + { "type": "object", + "properties": { + "or": { "type": "array", + "items": { "$ref": "List.Filter.Albums" }, + "minItems": 1, "required": true + } + } + }, + { "$ref": "List.Filter.Rule.Albums" } + ] + }, + "List.Filter.Songs": { + "type": [ + { "type": "object", + "properties": { + "and": { "type": "array", + "items": { "$ref": "List.Filter.Songs" }, + "minItems": 1, "required": true + } + } + }, + { "type": "object", + "properties": { + "or": { "type": "array", + "items": { "$ref": "List.Filter.Songs" }, + "minItems": 1, "required": true + } + } + }, + { "$ref": "List.Filter.Rule.Songs" } + ] + }, + "List.Filter.Textures": { + "type": [ + { "type": "object", + "properties": { + "and": { "type": "array", + "items": { "$ref": "List.Filter.Textures" }, + "minItems": 1, "required": true + } + } + }, + { "type": "object", + "properties": { + "or": { "type": "array", + "items": { "$ref": "List.Filter.Textures" }, + "minItems": 1, "required": true + } + } + }, + { "$ref": "List.Filter.Rule.Textures" } + ] + }, + "List.Item.Base": { + "extends": [ "Video.Details.File", "Audio.Details.Media" ], + "properties": { + "id": { "$ref": "Library.Id" }, + "type": { "type": "string", "enum": [ "unknown", "movie", "episode", "musicvideo", "song", "picture", "channel", "recording" ] }, + "albumartist": { "$ref": "Array.String" }, + "album": { "type": "string" }, + "track": { "type": "integer" }, + "duration": { "type": "integer" }, + "comment": { "type": "string" }, + "lyrics": { "type": "string" }, + "musicbrainztrackid": { "type": "string" }, + "musicbrainzartistid": { "$ref": "Array.String" }, + "trailer": { "type": "string" }, + "tagline": { "type": "string" }, + "plotoutline": { "type": "string" }, + "originaltitle": { "type": "string" }, + "writer": { "$ref": "Array.String" }, + "studio": { "$ref": "Array.String" }, + "mpaa": { "type": "string" }, + "cast": { "$ref": "Video.Cast" }, + "country": { "$ref": "Array.String" }, + "imdbnumber": { "type": "string" }, + "premiered": { "type": "string" }, + "productioncode": { "type": "string" }, + "set": { "type": "string" }, + "showlink": { "$ref": "Array.String" }, + "top250": { "type": "integer" }, + "votes": { "type": "string" }, + "firstaired": { "type": "string" }, + "season": { "type": "integer" }, + "episode": { "type": "integer" }, + "showtitle": { "type": "string" }, + "albumid": { "$ref": "Library.Id" }, + "setid": { "$ref": "Library.Id" }, + "tvshowid": { "$ref": "Library.Id" }, + "watchedepisodes": { "type": "integer" }, + "disc": { "type": "integer" }, + "tag": { "$ref": "Array.String" }, + "albumartistid": { "$ref": "Array.Integer" }, + "uniqueid": { "$ref": "Media.UniqueID" }, + "episodeguide": { "type": "string" }, + "sorttitle": { "type": "string" }, + "description": { "type": "string" }, + "theme": { "$ref": "Array.String" }, + "mood": { "$ref": "Array.String" }, + "style": { "$ref": "Array.String" }, + "albumlabel": { "type": "string" }, + "specialsortseason": { "type": "integer" }, + "specialsortepisode": { "type": "integer" }, + "compilation": { "type": "boolean" }, + "releasetype": { "$ref": "Audio.Album.ReleaseType" }, + "albumreleasetype": { "$ref": "Audio.Album.ReleaseType" }, + "contributors": { "$ref": "Audio.Contributors" }, + "displaycomposer": { "type": "string"}, + "displayconductor": { "type": "string"}, + "displayorchestra": { "type": "string"}, + "displaylyricist": { "type": "string"}, + "mediapath": { "type": "string", "description": "Media source path that identifies the item"}, + "dynpath": { "type": "string", "description": "An experimental property for debug purposes, often same as mediapath but when different gives the actual file playing that should also be in file property"}, + "isboxset": { "type": "boolean" }, + "totaldiscs": { "type": "integer" }, + "disctitle": { "type": "string" }, + "releasedate": { "type": "string" }, + "originaldate": { "type": "string" }, + "bpm": { "type": "integer" }, + "bitrate": { "type": "integer" }, + "samplerate": { "type": "integer" }, + "channels": { "type": "integer"}, + "albumstatus": { "type": "string" }, + "customproperties": { "$ref": "Item.CustomProperties" } + } + }, + "List.Fields.All": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "title", "artist", "albumartist", "genre", "year", "rating", + "album", "track", "duration", "comment", "lyrics", "musicbrainztrackid", + "musicbrainzartistid", "musicbrainzalbumid", "musicbrainzalbumartistid", + "playcount", "fanart", "director", "trailer", "tagline", "plot", + "plotoutline", "originaltitle", "lastplayed", "writer", "studio", + "mpaa", "cast", "country", "imdbnumber", "premiered", "productioncode", + "runtime", "set", "showlink", "streamdetails", "top250", "votes", + "firstaired", "season", "episode", "showtitle", "thumbnail", "file", + "resume", "artistid", "albumid", "tvshowid", "setid", "watchedepisodes", + "disc", "tag", "art", "genreid", "displayartist", "albumartistid", + "description", "theme", "mood", "style", "albumlabel", "sorttitle", + "episodeguide", "uniqueid", "dateadded", "channel", "channeltype", "hidden", + "locked", "channelnumber", "subchannelnumber", "starttime", "endtime", + "specialsortseason", "specialsortepisode", "compilation", "releasetype", + "albumreleasetype", "contributors", "displaycomposer", "displayconductor", + "displayorchestra", "displaylyricist", "userrating", "votes", "sortartist", + "musicbrainzreleasegroupid", "mediapath", "dynpath", "isboxset", "totaldiscs", + "disctitle", "releasedate", "originaldate", "bpm", "bitrate", "samplerate", + "channels", "albumstatus", "datemodified", "datenew", "customproperties", + "albumduration"] + } + }, + "List.Item.All": { + "extends": "List.Item.Base", + "properties": { + "channel": { "type": "string" }, + "channeltype": { "$ref": "PVR.Channel.Type" }, + "hidden": { "type": "boolean" }, + "locked": { "type": "boolean" }, + "channelnumber": { "type": "integer" }, + "subchannelnumber": { "type": "integer" }, + "starttime": { "type": "string" }, + "endtime": { "type": "string" } + } + }, + "List.Fields.Files": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "title", "artist", "albumartist", "genre", "year", "rating", + "album", "track", "duration", "comment", "lyrics", "musicbrainztrackid", + "musicbrainzartistid", "musicbrainzalbumid", "musicbrainzalbumartistid", + "playcount", "fanart", "director", "trailer", "tagline", "plot", + "plotoutline", "originaltitle", "lastplayed", "writer", "studio", + "mpaa", "cast", "country", "imdbnumber", "premiered", "productioncode", + "runtime", "set", "showlink", "streamdetails", "top250", "votes", + "firstaired", "season", "episode", "showtitle", "thumbnail", "file", + "resume", "artistid", "albumid", "tvshowid", "setid", "watchedepisodes", + "disc", "tag", "art", "genreid", "displayartist", "albumartistid", + "description", "theme", "mood", "style", "albumlabel", "sorttitle", + "episodeguide", "uniqueid", "dateadded", "size", "lastmodified", "mimetype", + "specialsortseason", "specialsortepisode", "sortartist", "musicbrainzreleasegroupid", + "isboxset", "totaldiscs", "disctitle", "releasedate", "originaldate", "bpm", + "bitrate", "samplerate", "channels", "datemodified", "datenew", "customproperties", + "albumduration", "userrating"] + } + }, + "List.Item.File": { + "extends": "List.Item.Base", + "properties": { + "file": { "type": "string", "required": true }, + "filetype": { "type": "string", "enum": [ "file", "directory" ], "required": true }, + "size": { "type": "integer", "description": "Size of the file in bytes" }, + "lastmodified": { "type": "string" }, + "mimetype": { "type": "string" } + } + }, + "List.Items.Sources": { + "type": "array", + "items": { + "extends": "Item.Details.Base", + "properties": { + "file": { "type": "string", "required": true } + } + } + }, + "Addon.Content": { + "type": "string", + "enum": [ "unknown", "video", "audio", "image", "executable" ], + "default": "unknown" + }, + "Addon.Fields": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "name", "version", "summary", "description", "path", "author", "thumbnail", "disclaimer", "fanart", + "dependencies", "broken", "extrainfo", "rating", "enabled", "installed", "deprecated" ] + } + }, + "Addon.Details": { + "extends": "Item.Details.Base", + "properties": { + "addonid": { "type": "string", "required": true }, + "type": { "$ref": "Addon.Types", "required": true }, + "name": { "type": "string" }, + "version": { "type": "string" }, + "summary": { "type": "string" }, + "description": { "type": "string" }, + "path": { "type": "string" }, + "author": { "type": "string" }, + "thumbnail": { "type": "string" }, + "disclaimer": { "type": "string" }, + "fanart": { "type": "string" }, + "dependencies": { "type": "array", + "items": { "type": "object", + "properties": { + "addonid": { "type": "string", "required": true }, + "version": { "type": "string", "required": true }, + "optional": { "type": "boolean", "required": true } + } + } + }, + "broken": { "type": [ "boolean", "string" ] }, + "extrainfo": { "type": "array", + "items": { "type": "object", + "properties": { + "key": { "type": "string", "required": true }, + "value": { "type": "string", "required": true } + } + } + }, + "rating": { "type": "integer" }, + "enabled": { "type": "boolean" }, + "installed": { "type": "boolean" }, + "deprecated": { "type": [ "boolean", "string" ] } + } + }, + "GUI.Stereoscopy.Mode": { + "type": "object", + "properties": { + "mode": { "type": "string", "required": true, "enum": [ "off", "split_vertical", "split_horizontal", "row_interleaved", "hardware_based", "anaglyph_cyan_red", "anaglyph_green_magenta", "anaglyph_yellow_blue", "monoscopic" ] }, + "label": { "type": "string", "required": true } + } + }, + "GUI.Property.Name": { + "type": "string", + "enum": [ "currentwindow", "currentcontrol", "skin", "fullscreen", "stereoscopicmode" ] + }, + "GUI.Property.Value": { + "type": "object", + "properties": { + "currentwindow": { "type": "object", + "properties": { + "id": { "type": "integer", "required": true }, + "label": { "type": "string", "required": true } + } + }, + "currentcontrol": { "type": "object", + "properties": { + "label": { "type": "string", "required": true } + } + }, + "skin": { "type": "object", + "properties": { + "id": { "type": "string", "required": true, "minLength": 1 }, + "name": { "type": "string" } + } + }, + "fullscreen": { "type": "boolean" }, + "stereoscopicmode": { "$ref": "GUI.Stereoscopy.Mode" } + } + }, + "System.Property.Name": { + "type": "string", + "enum": [ "canshutdown", "cansuspend", "canhibernate", "canreboot" ] + }, + "System.Property.Value": { + "type": "object", + "properties": { + "canshutdown": { "type": "boolean" }, + "cansuspend": { "type": "boolean" }, + "canhibernate": { "type": "boolean" }, + "canreboot": { "type": "boolean" } + } + }, + "Application.Property.Name": { + "type": "string", + "enum": [ "volume", "muted", "name", "version", "volume", "sorttokens", "language" ] + }, + "Application.Property.Value": { + "type": "object", + "properties": { + "volume": { "type": "integer", "minimum": 0, "maximum": 100 }, + "muted": { "type": "boolean" }, + "name": { "type": "string", "minLength": 1 }, + "version": { "type": "object", + "properties": { + "major": { "type": "integer", "minimum": 0, "required": true }, + "minor": { "type": "integer", "minimum": 0, "required": true }, + "revision": { "type": [ "string", "integer" ] }, + "tag": { "type": "string", "enum": [ "prealpha", "alpha", "beta", "releasecandidate", "stable" ], "required": true }, + "tagversion": { "type": "string" } + } + }, + "sorttokens": { "$ref": "Array.String", "description": "Articles ignored during sorting when ignorearticle is enabled." }, + "language": { "type": "string", "minLength": 1, "description": "Current language code and region e.g. en_GB" } + } + }, + "Favourite.Fields.Favourite": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "window", "windowparameter", "thumbnail", "path" ] + } + }, + "Favourite.Type": { + "type": "string", + "enum": [ "media", "window", "script", "androidapp", "unknown" ] + }, + "Favourite.Details.Favourite": { + "type": "object", + "properties": { + "title": { "type": "string", "required": true }, + "type": { "$ref": "Favourite.Type", "required": true }, + "path": { "type": "string" }, + "window": { "type": "string" }, + "windowparameter": { "type": "string" }, + "thumbnail": { "type": "string" } + }, + "additionalProperties": false + }, + "Setting.Type": { + "type": "string", + "enum": [ + "boolean", "integer", "number", "string", "action", "list", + "path", "addon", "date", "time" + ] + }, + "Setting.Level": { + "type": "string", + "enum": [ "basic", "standard", "advanced", "expert" ] + }, + "Setting.Value": { + "type": [ + { "type": "boolean", "required": true }, + { "type": "integer", "required": true }, + { "type": "number", "required": true }, + { "type": "string", "required": true } + ] + }, + "Setting.Value.List": { + "type": "array", + "items": { "$ref": "Setting.Value" } + }, + "Setting.Value.Extended": { + "type": [ + { "type": "boolean", "required": true }, + { "type": "integer", "required": true }, + { "type": "number", "required": true }, + { "type": "string", "required": true }, + { "$ref": "Setting.Value.List", "required": true } + ] + }, + "Setting.Details.ControlBase": { + "type": "object", + "properties": { + "type": { "type": "string", "required": true }, + "format": { "type": "string", "required": true }, + "delayed": { "type": "boolean", "required": true } + } + }, + "Setting.Details.ControlCheckmark": { + "extends": "Setting.Details.ControlBase", + "properties": { + "type": { "type": "string", "required": true, "enum": [ "toggle" ] }, + "format": { "type": "string", "required": true, "enum": [ "boolean" ] } + } + }, + "Setting.Details.ControlSpinner": { + "extends": "Setting.Details.ControlBase", + "properties": { + "type": { "type": "string", "required": true, "enum": [ "spinner" ] }, + "formatlabel": { "type": "string" }, + "minimumlabel": { "type": "string" } + } + }, + "Setting.Details.ControlHeading": { + "extends": "Setting.Details.ControlBase", + "properties": { + "heading": { "type": "string" } + } + }, + "Setting.Details.ControlEdit": { + "extends": "Setting.Details.ControlHeading", + "properties": { + "type": { "type": "string", "required": true, "enum": [ "edit" ] }, + "hidden": { "type": "boolean", "required": true }, + "verifynewvalue": { "type": "boolean", "required": true } + } + }, + "Setting.Details.ControlButton": { + "extends": "Setting.Details.ControlHeading", + "properties": { + "type": { "type": "string", "required": true, "enum": [ "button" ] } + } + }, + "Setting.Details.ControlList": { + "extends": "Setting.Details.ControlHeading", + "properties": { + "type": { "type": "string", "required": true, "enum": [ "list" ] }, + "multiselect": { "type": "boolean", "required": true } + } + }, + "Setting.Details.ControlSlider": { + "extends": "Setting.Details.ControlHeading", + "properties": { + "type": { "type": "string", "required": true, "enum": [ "slider" ] }, + "formatlabel": { "type": "string", "required": true }, + "popup": { "type": "boolean", "required": true } + } + }, + "Setting.Details.ControlRange": { + "extends": "Setting.Details.ControlBase", + "properties": { + "type": { "type": "string", "required": true, "enum": [ "range" ] }, + "formatlabel": { "type": "string", "required": true }, + "formatvalue": { "type": "string", "required": true } + } + }, + "Setting.Details.ControlLabel": { + "extends": "Setting.Details.ControlBase", + "properties": { + "type": { "type": "string", "required": true, "enum": [ "label" ] }, + "format": { "type": "string", "required": true, "enum": [ "string" ] } + } + }, + "Setting.Details.Control": { + "type": [ + { "$ref": "Setting.Details.ControlCheckmark", "required": true }, + { "$ref": "Setting.Details.ControlSpinner", "required": true }, + { "$ref": "Setting.Details.ControlEdit", "required": true }, + { "$ref": "Setting.Details.ControlButton", "required": true }, + { "$ref": "Setting.Details.ControlList", "required": true }, + { "$ref": "Setting.Details.ControlSlider", "required": true }, + { "$ref": "Setting.Details.ControlRange", "required": true }, + { "$ref": "Setting.Details.ControlLabel", "required": true } + ] + }, + "Setting.Details.Base": { + "type": "object", + "properties": { + "id": { "type": "string", "required": true, "minLength": 1 }, + "label": { "type": "string", "required": true }, + "help": { "type": "string" } + } + }, + "Setting.Details.SettingBase": { + "extends": "Setting.Details.Base", + "properties": { + "type": { "$ref": "Setting.Type", "required": true }, + "enabled": { "type": "boolean", "required": true }, + "level": { "$ref": "Setting.Level", "required": true }, + "parent": { "type": "string" }, + "control": { "$ref": "Setting.Details.Control" } + }, + "additionalProperties": false + }, + "Setting.Details.SettingBool": { + "extends": "Setting.Details.SettingBase", + "properties": { + "value": { "type": "boolean", "required": true }, + "default": { "type": "boolean", "required": true } + }, + "additionalProperties": false + }, + "Setting.Details.SettingInt": { + "extends": "Setting.Details.SettingBase", + "properties": { + "value": { "type": "integer", "required": true }, + "default": { "type": "integer", "required": true }, + "minimum": { "type": "integer" }, + "step": { "type": "integer" }, + "maximum": { "type": "integer" }, + "options": { "type": "array", + "items": { "type": "object", + "properties": { + "label": { "type": "string", "required": true }, + "value": { "type": "integer", "required": true } + } + } + } + }, + "additionalProperties": false + }, + "Setting.Details.SettingNumber": { + "extends": "Setting.Details.SettingBase", + "properties": { + "value": { "type": "number", "required": true }, + "default": { "type": "number", "required": true }, + "minimum": { "type": "number", "required": true }, + "step": { "type": "number", "required": true }, + "maximum": { "type": "number", "required": true } + }, + "additionalProperties": false + }, + "Setting.Details.SettingString": { + "extends": "Setting.Details.SettingBase", + "properties": { + "value": { "type": "string", "required": true }, + "default": { "type": "string", "required": true }, + "allowempty": { "type": "boolean", "required": true }, + "options": { "type": "array", + "items": { "type": "object", + "properties": { + "label": { "type": "string", "required": true }, + "value": { "type": "string", "required": true } + } + } + } + } + }, + "Setting.Details.SettingAction": { + "extends": "Setting.Details.SettingBase", + "properties": { + "data": { "type": "string", "required": true } + }, + "additionalProperties": false + }, + "Setting.Details.SettingList": { + "extends": "Setting.Details.SettingBase", + "properties": { + "value": { "$ref": "Setting.Value.List", "required": true }, + "default": { "$ref": "Setting.Value.List", "required": true }, + "elementtype": { "$ref": "Setting.Type", "required": true }, + "definition": { "$ref": "Setting.Details.Setting", "required": true }, + "delimiter": { "type": "string", "required": true }, + "minimumItems": { "type": "integer" }, + "maximumItems": { "type": "integer" } + }, + "additionalProperties": false + }, + "Setting.Details.SettingPath": { + "extends": "Setting.Details.SettingString", + "properties": { + "writable": { "type": "boolean", "required": true }, + "sources": { "type": "array", "items": { "type": "string" } } + }, + "additionalProperties": false + }, + "Setting.Details.SettingAddon": { + "extends": "Setting.Details.SettingString", + "properties": { + "addontype": { "$ref": "Addon.Types", "required": true } + }, + "additionalProperties": false + }, + "Setting.Details.SettingDate": { + "extends": "Setting.Details.SettingString", + "additionalProperties": false + }, + "Setting.Details.SettingTime": { + "extends": "Setting.Details.SettingString", + "additionalProperties": false + }, + "Setting.Details.Setting": { + "type": [ + { "$ref": "Setting.Details.SettingBool", "required": true }, + { "$ref": "Setting.Details.SettingInt", "required": true }, + { "$ref": "Setting.Details.SettingNumber", "required": true }, + { "$ref": "Setting.Details.SettingString", "required": true }, + { "$ref": "Setting.Details.SettingAction", "required": true }, + { "$ref": "Setting.Details.SettingList", "required": true }, + { "$ref": "Setting.Details.SettingPath", "required": true }, + { "$ref": "Setting.Details.SettingAddon", "required": true }, + { "$ref": "Setting.Details.SettingDate", "required": true }, + { "$ref": "Setting.Details.SettingTime", "required": true } + ] + }, + "Setting.Details.Group": { + "type": "object", + "properties": { + "id": { "type": "string", "required": true, "minLength": 1 }, + "settings": { + "type": "array", + "items": { "$ref": "Setting.Details.Setting" }, + "minItems": 1, + "uniqueItems": true + } + }, + "additionalProperties": false + }, + "Setting.Details.Category": { + "extends": "Setting.Details.Base", + "properties": { + "groups": { + "type": "array", + "items": { "$ref": "Setting.Details.Group" }, + "minItems": 1, + "uniqueItems": true + } + }, + "additionalProperties": false + }, + "Setting.Details.Section": { + "extends": "Setting.Details.Base", + "properties": { + "categories": { + "type": "array", + "items": { "$ref": "Setting.Details.Category" }, + "minItems": 1, + "uniqueItems": true + } + }, + "additionalProperties": false + } +} diff --git a/xbmc/interfaces/json-rpc/schema/version.txt b/xbmc/interfaces/json-rpc/schema/version.txt new file mode 100644 index 0000000..40bfd87 --- /dev/null +++ b/xbmc/interfaces/json-rpc/schema/version.txt @@ -0,0 +1 @@ +JSONRPC_VERSION 13.0.0 |