Adding profiles support to the JSON-RPC and to the default web server.
<?xml version="1.0" encoding="UTF-8"?>
<addon
id="webinterface.default"
- version="2.1.11"
+ version="2.2.0"
name="Default"
provider-name="Team XBMC">
<requires>
padding: 10px 10px 15px 10px;
}
-.floatableMovieCover {
+.floatableMovieCover,
+.floatableProfileThumb {
float: left;
width: 130px;
height: 200px;
.floatableAlbum:hover,
.floatableTVShowCover:hover,
-.floatableMovieCover:hover {
+.floatableMovieCover:hover,
+.floatableProfileThumb:hover {
background: #aeaeae;
}
#libraryContainer .floatableAlbum,
#movieLibraryContainer .floatableMovieCover,
+#profilesContainer .floatableProfileThumb,
#tvshowLibraryContainer .floatableTVShowCover {
cursor: pointer;
}
.floatableAlbum div.imgWrapper,
.floatableMovieCover div.imgWrapper,
+.floatableProfileThumb div.imgWrapper,
.floatableTVShowCover div.imgWrapper {
width: 130px;
height: 130px;
}
.floatableMovieCover div.imgWrapper,
-.floatableMovieCover div.imgWrapper div.inner {
+.floatableMovieCover div.imgWrapper div.inner,
+.floatableProfileThumb div.imgWrapper,
+.floatableProfileThumb div.imgWrapper div.inner {
height: 190px;
}
width: 130px;
}
-.floatableMovieCover img {
+.floatableMovieCover img,
+.floatableProfileThumb img {
height: 180px;
}
.floatableAlbum p.album,
-.floatableMovieCover p.album {
+.floatableMovieCover p.album,
+.floatableProfileThumb p.album {
font-size: 12px;
font-weight: 700;
color: #000;
}
.floatableAlbum p.artist,
-.floatableMovieCover p.artist {
+.floatableMovieCover p.artist,
+.floatableProfileThumb p.artist {
font-size: 11px;
color: #777;
text-align: center;
<div id="commsErrorPanel" style="display: none;"></div>
<div id="navigation">
<ul>
+ <li id="profiles">Profiles</li>
<li id="remoteControl">Remote</li>
<li id="movieLibrary">Movies</li>
<li id="tvshowLibrary">TV Shows</li>
$('#tvshowLibrary').click(jQuery.proxy(this.tvshowLibraryOpen, this));
$('#pictureLibrary').click(jQuery.proxy(this.pictureLibraryOpen, this));
$('#remoteControl').click(jQuery.proxy(this.remoteControlOpen, this));
+ $('#profiles').click(jQuery.proxy(this.profilesOpen, this));
$('#overlay').click(jQuery.proxy(this.hideOverlay, this));
$(window).resize(jQuery.proxy(this.updatePlayButtonLocation, this));
$(document).on('keydown', jQuery.proxy(this.handleKeyPress, this));
$('#tvshowLibrary').removeClass('selected');
$('#remoteControl').removeClass('selected');
$('#pictureLibrary').removeClass('selected');
+ $('#profilesLibrary').removeClass('selected');
this.hideOverlay();
},
replaceAll: function (haystack, needle, thread) {
className = 'floatableAlbum';
code = '<p class="album" title="' + title + '">' + showTitle + '</p>';
break;
+ case 'profile':
+ className = 'floatableProfileThumb';
+ code = '<p class="album" title="' + title + '">' + showTitle + '</p>';
+ break;
}
return floatableAlbum.addClass(className).html('<div class="imgWrapper"><div class="inner"><img src="' + path + '" alt="' + title + '" /></div></div>' + code);
},
}
});
},
+ loadProfile: function (event) {
+ return xbmc.rpc.request({
+ 'context': this,
+ 'method': 'Profiles.LoadProfile',
+ 'params': {
+ 'profile': event.data.profile.label
+ }
+ });
+ },
movieLibraryOpen: function () {
this.resetPage();
$('#movieLibrary').addClass('selected');
libraryContainer.trigger('scroll');
}
},
+ profilesOpen: function () {
+ this.resetPage();
+ $('#profiles').addClass('selected');
+ $('.contentContainer').hide();
+ var libraryContainer = $('#profilesContainer');
+ if (!libraryContainer || libraryContainer.length == 0) {
+ $('#spinner').show();
+ var currentProfile = "";
+ xbmc.rpc.request({
+ 'method': 'Profiles.GetCurrentProfile',
+ 'params': {
+ 'properties': [
+ 'lockmode'
+ ]
+ },
+ 'success': function (data) {
+ if (data)
+ if (data.result)
+ currentProfile = data.result.label;
+ }
+ });
+ xbmc.rpc.request({
+ 'context': this,
+ 'method': 'Profiles.GetProfiles',
+ 'params': {
+ 'limits': {
+ 'start': 0
+ },
+ 'properties': [
+ 'thumbnail'
+ ],
+ 'sort': {
+ 'method': 'sorttitle',
+ 'ignorearticle': true
+ }
+ },
+ 'success': function (data) {
+ if (data && data.result && data.result.profiles) {
+ libraryContainer = $('<div>');
+ libraryContainer.attr('id', 'profilesContainer')
+ .addClass('contentContainer');
+ $('#content').append(libraryContainer);
+ } else {
+ libraryContainer.html('');
+ }
+ $.each($(data.result.profiles), jQuery.proxy(function (i, item) {
+ var itemLabel = item.label;
+ if (currentProfile == itemLabel)
+ {
+ itemLabel = itemLabel + "*";
+ }
+ var floatableProfileThumb = this.generateThumb('profile', item.thumbnail, itemLabel);
+ floatableProfileThumb.bind('click', { profile: item }, jQuery.proxy(this.loadProfile, this));
+ libraryContainer.append(floatableProfileThumb);
+ }, this));
+ libraryContainer.append($('<div>').addClass('footerPadding'));
+ $('#spinner').hide();
+ libraryContainer.bind('scroll', { activeLibrary: libraryContainer }, jQuery.proxy(this.updateScrollEffects, this));
+ libraryContainer.trigger('scroll');
+ myScroll = new iScroll('profilesContainer');
+ }
+ });
+ } else {
+ libraryContainer.show();
+ libraryContainer.trigger('scroll');
+ }
+ },
updateScrollEffects: function (event) {
if (event.data.activeLibrary && $(event.data.activeLibrary).scrollTop() > 0) {
$('#topScrollFade').fadeIn();
<ClCompile Include="..\..\xbmc\interfaces\json-rpc\JSONServiceDescription.cpp" />
<ClCompile Include="..\..\xbmc\interfaces\json-rpc\PlayerOperations.cpp" />
<ClCompile Include="..\..\xbmc\interfaces\json-rpc\PlaylistOperations.cpp" />
+ <ClCompile Include="..\..\xbmc\interfaces\json-rpc\ProfilesOperations.cpp" />
<ClCompile Include="..\..\xbmc\interfaces\json-rpc\PVROperations.cpp" />
<ClCompile Include="..\..\xbmc\interfaces\json-rpc\SystemOperations.cpp" />
<ClCompile Include="..\..\xbmc\interfaces\json-rpc\VideoLibrary.cpp" />
<ClInclude Include="..\..\xbmc\interfaces\generic\LanguageInvokerThread.h" />
<ClInclude Include="..\..\xbmc\interfaces\generic\ScriptInvocationManager.h" />
<ClInclude Include="..\..\xbmc\interfaces\json-rpc\FavouritesOperations.h" />
+ <ClInclude Include="..\..\xbmc\interfaces\json-rpc\ProfilesOperations.h" />
<ClInclude Include="..\..\xbmc\interfaces\json-rpc\PVROperations.h" />
<ClInclude Include="..\..\xbmc\interfaces\legacy\Addon.h" />
<ClInclude Include="..\..\xbmc\interfaces\legacy\AddonCallback.h" />
</VisualStudio>
</ProjectExtensions>
<Import Project="$(SolutionDir)\$(ProjectFileName).targets.user" Condition="Exists('$(SolutionDir)\$(ProjectFileName).targets.user')" />
-</Project>
\ No newline at end of file
+</Project>
<ClCompile Include="..\..\xbmc\addons\AddonCallbacksCodec.cpp">
<Filter>addons</Filter>
</ClCompile>
+ <ClCompile Include="..\..\xbmc\interfaces\json-rpc\ProfilesOperations.cpp">
+ <Filter>interfaces\json-rpc</Filter>
+ </ClCompile>
<ClCompile Include="..\..\xbmc\GitRevision.cpp" />
<ClCompile Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAE.cpp">
<Filter>cores\AudioEngine\Engines\ActiveAE</Filter>
<ClInclude Include="..\..\xbmc\addons\AddonCallbacksCodec.h">
<Filter>addons</Filter>
</ClInclude>
+ <ClInclude Include="..\..\xbmc\interfaces\json-rpc\ProfilesOperations.h">
+ <Filter>interfaces\json-rpc</Filter>
+ </ClInclude>
<ClInclude Include="..\..\xbmc\win32\PlatformInclude.h" />
<ClInclude Include="..\..\xbmc\cores\AudioEngine\Engines\ActiveAE\ActiveAE.h">
<Filter>cores\AudioEngine\Engines\ActiveAE</Filter>
<Filter>interfaces\swig</Filter>
</None>
</ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
#include "XBMCOperations.h"
#include "ApplicationOperations.h"
#include "PVROperations.h"
+#include "ProfilesOperations.h"
#include "FavouritesOperations.h"
using namespace std;
{ "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 },
JSONServiceDescription.cpp \
PlayerOperations.cpp \
PlaylistOperations.cpp \
+ ProfilesOperations.cpp \
PVROperations.cpp \
SystemOperations.cpp \
VideoLibrary.cpp \
--- /dev/null
+/*
+ * Copyright (C) 2013 Team XBMC
+ * http://xbmc.org
+ *
+ * This Program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This Program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XBMC; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "ProfilesOperations.h"
+#include "ApplicationMessenger.h"
+#include "guilib/LocalizeStrings.h"
+#include "profiles/ProfilesManager.h"
+#include "utils/md5.h"
+
+using namespace JSONRPC;
+
+JSONRPC_STATUS CProfilesOperations::GetProfiles(const CStdString &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
+{
+ CFileItemList listItems;
+
+ for (unsigned int i = 0; i < CProfilesManager::Get().GetNumberOfProfiles(); ++i)
+ {
+ const CProfile *profile = CProfilesManager::Get().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)
+ {
+ CStdString profilename = (*profileiter)["label"].asString();
+ int index = CProfilesManager::Get().GetProfileIndex(profilename);
+ const CProfile *profile = CProfilesManager::Get().GetProfile(index);
+ LockType locktype = LOCK_MODE_UNKNOWN;
+ if (index == 0)
+ locktype = CProfilesManager::Get().GetMasterProfile().getLockMode();
+ else
+ locktype = profile->getLockMode();
+ (*profileiter)["lockmode"] = locktype;
+ }
+ break;
+ }
+ }
+ return OK;
+}
+
+JSONRPC_STATUS CProfilesOperations::GetCurrentProfile(const CStdString &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
+{
+ const CProfile& currentProfile = CProfilesManager::Get().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 CStdString &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result)
+{
+ CStdString profilename = parameterObject["profile"].asString();
+ int index = CProfilesManager::Get().GetProfileIndex(profilename);
+
+ if (index < 0)
+ return InvalidParams;
+
+ // Init prompt
+ bool bPrompt = false;
+ bPrompt = parameterObject["prompt"].asBoolean();
+
+ bool bCanceled;
+ bool bLoadProfile(false);
+
+ if (CProfilesManager::Get().GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE || // Password not needed
+ (bPrompt && g_passwordManager.IsProfileLockUnlocked(index, bCanceled, bPrompt))) // Password needed and user asked to enter it
+ bLoadProfile = true;
+ else if (!bCanceled && parameterObject.isMember("password")) // Password needed and user provided it
+ {
+ const CVariant &passwordObject = parameterObject["password"];
+ CStdString strToVerify; // Holds user saved password hash
+ if (index == 0)
+ strToVerify = CProfilesManager::Get().GetMasterProfile().getLockCode();
+ else
+ {
+ CProfile *profile = CProfilesManager::Get().GetProfile(index);
+ strToVerify = profile->getLockCode();
+ }
+
+ CStdString password = passwordObject["value"].asString();
+
+ // Create password hash from the provided password if md5 is not used
+ CStdString md5pword2;
+ CStdString encryption = passwordObject["encryption"].asString();
+ if (encryption.Equals("none"))
+ {
+ XBMC::XBMC_MD5 md5state;
+ md5state.append(password);
+ md5state.getDigest(md5pword2);
+ }
+ else if (encryption.Equals("md5"))
+ md5pword2 = password;
+
+ // Verify profided password
+ if (strToVerify.Equals(md5pword2))
+ bLoadProfile = true;
+ }
+
+ if (bLoadProfile)
+ {
+ CApplicationMessenger::Get().LoadProfile(index);
+ return ACK;
+ }
+ return InvalidParams;
+}
--- /dev/null
+#pragma once
+/*
+ * Copyright (C) 2013 Team XBMC
+ * http://xbmc.org
+ *
+ * This Program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This Program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XBMC; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "utils/StdString.h"
+#include "JSONRPC.h"
+#include "FileItemHandler.h"
+
+namespace JSONRPC
+{
+ class CProfilesOperations : CFileItemHandler
+ {
+ public:
+ static JSONRPC_STATUS GetProfiles(const CStdString &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result);
+ static JSONRPC_STATUS GetCurrentProfile(const CStdString &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result);
+ static JSONRPC_STATUS LoadProfile(const CStdString &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result);
+ };
+}
namespace JSONRPC
{
const char* const JSONRPC_SERVICE_ID = "http://www.xbmc.org/jsonrpc/ServiceDescription.json";
- const char* const JSONRPC_SERVICE_VERSION = "6.5.3";
+ const char* const JSONRPC_SERVICE_VERSION = "6.6.0";
const char* const JSONRPC_SERVICE_DESCRIPTION = "JSON-RPC API of XBMC";
const char* const JSONRPC_SERVICE_TYPES[] = {
"}"
"}"
"}",
- "\"List.Filter.Rule\": {"
+ "\"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 },"
"\"params\": [ ],"
"\"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\","
"params": [ ],
"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",
}
}
},
+ "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": {