aboutsummaryrefslogtreecommitdiffstats
path: root/LibOVR/Src/OVR_Profile.cpp
diff options
context:
space:
mode:
authorBrad Davis <[email protected]>2013-12-29 01:12:14 -0800
committerBrad Davis <[email protected]>2013-12-29 01:12:14 -0800
commit4ec40ed86509d5b8aaf18ac038836873bb45463c (patch)
tree4192974eb3dab2bac8ffdb508a3ec2936c351aaf /LibOVR/Src/OVR_Profile.cpp
parenta91c41e4b21b61c91df3c86c105a1ab08c6d77c6 (diff)
Updating JSON profile serialization
Diffstat (limited to 'LibOVR/Src/OVR_Profile.cpp')
-rw-r--r--LibOVR/Src/OVR_Profile.cpp1013
1 files changed, 446 insertions, 567 deletions
diff --git a/LibOVR/Src/OVR_Profile.cpp b/LibOVR/Src/OVR_Profile.cpp
index 3be43db..bf8906e 100644
--- a/LibOVR/Src/OVR_Profile.cpp
+++ b/LibOVR/Src/OVR_Profile.cpp
@@ -5,9 +5,9 @@ Filename : OVR_Profile.cpp
Content : Structs and functions for loading and storing device profile settings
Created : February 14, 2013
Notes :
-
+
Profiles are used to store per-user settings that can be transferred and used
- across multiple applications. For example, player IPD can be configured once
+ across multiple applications. For example, player IPD can be configured once
and reused for a unified experience across games. Configuration and saving of profiles
can be accomplished in game via the Profile API or by the official Oculus Configuration
Utility.
@@ -21,12 +21,13 @@ otherwise accompanies this software in either electronic or hard copy form.
************************************************************************************/
#include "OVR_Profile.h"
-#include "OVR_JSON.h"
+#include "OVR_Log.h"
#include "Kernel/OVR_Types.h"
#include "Kernel/OVR_SysFile.h"
#include "Kernel/OVR_Allocator.h"
#include "Kernel/OVR_Array.h"
-
+#include <json/json.h>
+#include <fstream>
#ifdef OVR_OS_WIN32
#include <Shlobj.h>
#else
@@ -40,8 +41,33 @@ otherwise accompanies this software in either electronic or hard copy form.
#endif
-#define PROFILE_VERSION 1.0
-#define MAX_PROFILE_MAJOR_VERSION 1
+
+#define PROFILE_VERSION 2
+#define MAX_PROFILE_MAJOR_VERSION 2
+
+// Many hard coded strings used in numerous locations have been
+// repositioned here, so that there's no chance of a misspelling
+// causing a problem. Not every string has been moved, but most of the
+// repeated ones have.
+#define KEY_PROFILE_VERSION "Oculus Profile Version"
+#define KEY_CURRENT_PROFILE "CurrentProfile"
+#define KEY_PROFILES "Profiles"
+#define KEY_DEVICES "Devices"
+#define KEY_GENDER "Gender"
+#define KEY_PLAYER_HEIGHT "PlayerHeight"
+#define KEY_IPD "IPD"
+#define KEY_STRABISMUS_CORRECTION "StrabismusCorrection"
+
+#define KEY_LL "LL"
+#define KEY_LR "LR"
+#define KEY_RL "RL"
+#define KEY_RR "RR"
+#define KEY_EYECUP "EyeCup"
+#define EPSILON 0.00001f
+// 5'10" inch man
+#define DEFAULT_HEIGHT 1.778f
+#define DEFAULT_IPD 0.064f
+
namespace OVR {
@@ -56,7 +82,7 @@ String GetBaseOVRPath(bool create_dir)
TCHAR data_path[MAX_PATH];
SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, NULL, 0, data_path);
path = String(data_path);
-
+
path += "/Oculus";
if (create_dir)
@@ -67,11 +93,11 @@ String GetBaseOVRPath(bool create_dir)
DWORD attrib = GetFileAttributes(wpath);
bool exists = attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY);
if (!exists)
- {
+ {
CreateDirectory(wpath, NULL);
}
}
-
+
#elif defined(OVR_OS_MAC)
const char* home = getenv("HOME");
@@ -92,11 +118,21 @@ String GetBaseOVRPath(bool create_dir)
}
#else
-
- passwd* pwd = getpwuid(getuid());
- const char* home = pwd->pw_dir;
- path = home;
- path += "/.config/Oculus";
+ // Updated the config folder location logic to rely on the
+ // XDG specification for config locations (the XDK location was being
+ // used anyway, but was hardcoded). This is analgous to using
+ // SHGetFolderPath in the windows implementation, rather than
+ // hardcoding %HOME%/AppData/Local
+ const char * config_home = getenv("XDG_CONFIG_HOME");
+ if (NULL != config_home) {
+ path = config_home;
+ } else {
+ // only if XDG_CONFIG_HOME is unses does the specification say to
+ // fallback on the default of $HOME/.config
+ path = getenv("HOME");
+ path += "/.config";
+ }
+ path += "/Oculus";
if (create_dir)
{ // Create the Oculus directory if it doesn't exist
@@ -129,7 +165,7 @@ String GetProfilePath(bool create_dir)
ProfileManager::ProfileManager()
{
Changed = false;
- CacheDevice = Profile_Unknown;
+ Loaded = false;
}
ProfileManager::~ProfileManager()
@@ -146,410 +182,423 @@ ProfileManager* ProfileManager::Create()
return new ProfileManager();
}
-Profile* ProfileManager::CreateProfileObject(const char* user,
- ProfileType device,
- const char** device_name)
-{
- Lock::Locker lockScope(&ProfileLock);
-
- Profile* profile = NULL;
- switch (device)
- {
- case Profile_GenericHMD:
- *device_name = NULL;
- profile = new HMDProfile(Profile_GenericHMD, user);
- break;
- case Profile_RiftDK1:
- *device_name = "RiftDK1";
- profile = new RiftDK1Profile(user);
- break;
- case Profile_RiftDKHD:
- *device_name = "RiftDKHD";
- profile = new RiftDKHDProfile(user);
- break;
- case Profile_Unknown:
- break;
- }
-
- return profile;
-}
-
-
// Clear the local profile cache
void ProfileManager::ClearCache()
{
Lock::Locker lockScope(&ProfileLock);
-
ProfileCache.Clear();
- CacheDevice = Profile_Unknown;
+ Loaded = false;
}
+// Profile loader is an intermediary that allows me to break down the
+// serialization into smaller pieces that work directly against the
+// new native JSON container type. This can't be done in the
+// ProfileManager because it can't declare functions referencing the
+// new type without polluting the header with knowledge of the JSON
+// implementation
+class ProfileLoader {
+public:
+ static void loadHmd(HmdDevice & device, const Json::Value & node) {
+ if (node.isNull()) {
+ return;
+ }
+ if (node.isMember(KEY_LL)) {
+ device.LL = node.get(KEY_LL, 0).asInt();
+ }
+ if (node.isMember(KEY_LR)) {
+ device.LR = node.get(KEY_LR, 0).asInt();
+ }
+ if (node.isMember(KEY_RL)) {
+ device.RL = node.get(KEY_RL, 0).asInt();
+ }
+ if (node.isMember(KEY_RR)) {
+ device.RR = node.get(KEY_RR, 0).asInt();
+ }
+ }
+
+ static void loadRift(RiftDevice & device, const Json::Value & node) {
+ if (node.isNull()) {
+ return;
+ }
+ String eyeCups = node.get(KEY_EYECUP, "A").asCString();
+ char c = eyeCups.GetSize() ? eyeCups.GetCharAt(0) : 'A';
+ switch (eyeCups.GetCharAt(0)) {
+ case 'A':
+ device.EyeCups = EyeCupType::EyeCup_A;
+ break;
+ case 'B':
+ device.EyeCups = EyeCupType::EyeCup_B;
+ break;
+ case 'C':
+ device.EyeCups = EyeCupType::EyeCup_C;
+ break;
+ }
+ loadHmd(device, node);
+ }
+
+ // TODO migrate all the string constants up
+ static void loadV1Profile(Profile & out, const Json::Value & node) {
+ if (node.isNull()) {
+ return;
+ }
+ String gender = node.get(KEY_GENDER, "Unknown").asCString();
+ if (gender == "Male") {
+ out.Gender = Profile::GenderType::Gender_Male;
+ } else if (gender == "Female") {
+ out.Gender = Profile::GenderType::Gender_Female;
+ } else {
+ out.Gender = Profile::GenderType::Gender_Unspecified;
+ }
+
+ out.PlayerHeight = node.get(KEY_PLAYER_HEIGHT, DEFAULT_HEIGHT).asFloat();
+ out.IPD = node.get(KEY_IPD, DEFAULT_IPD).asFloat();
+ loadHmd(out.Generic, node.get("GenericHMD", Json::Value::null));
+ loadRift(out.RiftDK1, node.get("RiftDK1", Json::Value::null));
+ loadRift(out.RiftDKHD, node.get("RiftDKHD", Json::Value::null));
+ }
+
+ static void loadProfile(Profile & out, const Json::Value & node) {
+ if (node.isNull()) {
+ return;
+ }
+ loadV1Profile(out, node);
+ if (node.isMember(KEY_STRABISMUS_CORRECTION)) {
+ loadQuaternion(out.StrabismusCorrection, node[KEY_STRABISMUS_CORRECTION]);
+ }
+ }
+
+ static void loadQuaternion(Quatf & out, const Json::Value & node) {
+ if (node.isNull()) {
+ return;
+ }
+ out.x = node.get("X", 0).asInt();
+ out.y = node.get("Y", 0).asInt();
+ out.z = node.get("Z", 0).asInt();
+ out.w = node.get("W", 1).asInt();
+ }
+
+ static void saveQuaternion(Json::Value & out, const Quatf & q) {
+ out["X"] = q.x;
+ out["Y"] = q.y;
+ out["Z"] = q.z;
+ out["W"] = q.w;
+ }
+
+ // TODO implement, and migrate the Devices.json parsing code to use this
+ static void loadMatrix(Matrix4f & out, const Json::Value & node) {
+ }
+
+ //
+ // The general pattern on writing Json is to write the node only if
+ // it differs from the default, and to explicitly remove the node if it
+ // is the same as the default. This is important, because the load / save
+ // mechanism is designed to preserve any pre-existing content and not
+ // to touch any fields it's not aware of.
+ //
+ // Therefore, failing to write out a default value doesn't mean there's
+ // not already a non-default value there, hence the explicit removes.
+ // It gets a little verbose but the result is greater extensibility of the
+ // profile data by third parties.
+ static void writeHmdDevice(Json::Value & out, const HmdDevice & device) {
+ if (device.LL != 0) {
+ out[KEY_LL] = device.LL;
+ } else {
+ out.removeMember(KEY_LL);
+ }
+ if (device.LR != 0) {
+ out[KEY_LR] = device.LR;
+ } else {
+ out.removeMember(KEY_LR);
+ }
+ if (device.RL != 0) {
+ out[KEY_RL] = device.RL;
+ } else {
+ out.removeMember(KEY_RL);
+ }
+ if (device.RR != 0) {
+ out[KEY_RR] = device.RR;
+ } else {
+ out.removeMember(KEY_RR);
+ }
+ }
+
+ static void writeRiftDevice(Json::Value & deviceNode, const RiftDevice & device) {
+ switch (device.EyeCups)
+ {
+ case EyeCup_B: deviceNode[KEY_EYECUP] = "B"; break;
+ case EyeCup_C: deviceNode[KEY_EYECUP] = "C"; break;
+ // A is the default, so no need to serialize it
+ case EyeCup_A: deviceNode.removeMember(KEY_EYECUP); break;
+ }
+ writeHmdDevice(deviceNode, device);
+ }
+
+ // Applying the "remove nodes if they're empty" logic here is a little
+ // arduous, but it makes the resulting JSON cleaner
+ static void updateDeviceProfile(Json::Value & parent, const HmdDevice & device) {
+ String name;
+ Json::Value newChild;
+ switch (device.GetDeviceType()) {
+ case Profile_RiftDK1:
+ name = "RiftDK1";
+ newChild = parent[name];
+ writeRiftDevice(newChild, (RiftDevice&)device);
+ break;
+
+ case Profile_RiftDKHD:
+ name = "RiftDKHD";
+ newChild = parent[name];
+ writeRiftDevice(newChild, (RiftDevice&)device);
+ break;
+
+ case Profile_GenericHMD:
+ name = "GenericHMD";
+ newChild = parent[name];
+ writeHmdDevice(newChild, device);
+ break;
+ }
+
+ // Don't write empty children
+ if (newChild.getMemberNames().size()) {
+ parent[name] = newChild;
+ } else {
+ parent.removeMember(name);
+ }
+ }
+
+ static void writeProfile(Json::Value & parent, const Profile & profile) {
+ Json::Value & out = parent[profile.Name];
+
+ switch (profile.GetGender()) {
+ case Profile::Gender_Male:
+ out[KEY_GENDER] = "Male";
+ break;
+ case Profile::Gender_Female:
+ out[KEY_GENDER] = "Female";
+ break;
+ default:
+ out.removeMember(KEY_GENDER);
+ }
+
+ // Epsilon is 10 micrometers. Smaller than a human hair
+ if (abs(profile.PlayerHeight - DEFAULT_HEIGHT) > EPSILON) {
+ out[KEY_PLAYER_HEIGHT] = profile.PlayerHeight;
+ } else {
+ out.removeMember(KEY_PLAYER_HEIGHT);
+ }
+
+ if (abs(profile.IPD - DEFAULT_IPD) > EPSILON) {
+ out[KEY_IPD] = profile.IPD;
+ } else {
+ out.removeMember(KEY_IPD);
+ }
+
+ if (profile.StrabismusCorrection.Distance(Quatf()) > EPSILON) {
+ saveQuaternion(out[KEY_STRABISMUS_CORRECTION],
+ profile.StrabismusCorrection);
+ } else {
+ out.removeMember(KEY_STRABISMUS_CORRECTION);
+ }
+
+ Json::Value & devicesNode = out[KEY_DEVICES];
+ updateDeviceProfile(devicesNode, profile.Generic);
+ updateDeviceProfile(devicesNode, profile.RiftDK1);
+ updateDeviceProfile(devicesNode, profile.RiftDKHD);
+ if (!devicesNode.getMemberNames().size()) {
+ out.removeMember(KEY_DEVICES);
+ }
+ }
+};
+
+// Use of STL IO is potentially avoidable with jsoncpp, if you wanted
+// to load the file into a String instead and use the char* interface for
+// parsing JSON in memory. Linking to C++ standard library is unavoidable
+// though, but it might not take much work to remove iostream usage from
+// the jsoncpp lib and make it use std::string only.
+bool parseJsonFile(const String & file, Json::Value & root) {
+ Json::Reader reader;
+ String path = GetProfilePath(false);
+ std::ifstream in(path.ToCStr());
+ if (!reader.parse(in, root)) {
+ // report to the user the failure and their locations in the document.
+ LogError("Failed to parse json file: %s\n %s",
+ file.ToCStr(),
+ reader.getFormattedErrorMessages().c_str());
+ return false;
+ }
+ return true;
+
+}
// Poplulates the local profile cache. This occurs on the first access of the profile
// data. All profile operations are performed against the local cache until the
// ProfileManager is released or goes out of scope at which time the cache is serialized
// to disk.
-void ProfileManager::LoadCache(ProfileType device)
-{
- Lock::Locker lockScope(&ProfileLock);
-
- ClearCache();
-
- String path = GetProfilePath(false);
-
- Ptr<JSON> root = *JSON::Load(path);
- if (!root || root->GetItemCount() < 3)
+void ProfileManager::LoadCache() {
+ if (Loaded) {
+ return;
+ }
+
+ Lock::Locker lockScope(&ProfileLock);
+ ClearCache();
+
+ Json::Value root;
+ if (!parseJsonFile(GetProfilePath(false), root)) {
+ LogError("Failed to parse configuration");
+ return;
+ }
+
+ if (!root.isMember(KEY_PROFILE_VERSION)) {
+ LogError("Profile JSON is malformed, missing version number");
+ return;
+ }
+
+ int major = root[KEY_PROFILE_VERSION].asInt();
+ switch (major) {
+ case 1: {
+ if (root.size() < 3) {
+ LogError("Profile JSON is malformed, insufficient keys");
return;
-
- // First read the file type and version to make sure this is a valid file
- JSON* item0 = root->GetFirstItem();
- JSON* item1 = root->GetNextItem(item0);
- JSON* item2 = root->GetNextItem(item1);
-
- if (item0->Name == "Oculus Profile Version")
- {
- int major = atoi(item0->Value.ToCStr());
- if (major > MAX_PROFILE_MAJOR_VERSION)
- return; // don't parse the file on unsupported major version number
- }
- else
- {
+ }
+ DefaultProfile = root[KEY_CURRENT_PROFILE].asCString();
+ const Json::Value & profileNode = root["Profile"];
+ String name = profileNode["Name"].asCString();
+ // The profile having to know it's own name is a symptom of the lack
+ // of a proper associative array class in the OVR codebase.
+ Profile * profile = new Profile(name);
+ ProfileLoader::loadV1Profile(*profile, profileNode);
+ ProfileCache.PushBack(profile);
+ }
+ break;
+
+ case 2: {
+ if (!root.isMember(KEY_PROFILES)) {
+ LogError("Missing profile data");
return;
- }
-
- DefaultProfile = item1->Value;
-
- // Read the number of profiles
- int profileCount = (int)item2->dValue;
- JSON* profileItem = item2;
-
- for (int p=0; p<profileCount; p++)
- {
- profileItem = root->GetNextItem(profileItem);
- if (profileItem == NULL)
- break;
-
- if (profileItem->Name == "Profile")
- {
- // Read the required Name field
- const char* profileName;
- JSON* item = profileItem->GetFirstItem();
-
- if (item && (item->Name == "Name"))
- {
- profileName = item->Value;
- }
- else
- {
- return; // invalid field
- }
-
- const char* deviceName = 0;
- bool deviceFound = false;
- Ptr<Profile> profile = *CreateProfileObject(profileName, device, &deviceName);
-
- // Read the base profile fields.
- if (profile)
- {
- while (item = profileItem->GetNextItem(item), item)
- {
- if (item->Type != JSON_Object)
- {
- profile->ParseProperty(item->Name, item->Value);
- }
- else
- { // Search for the matching device to get device specific fields
- if (!deviceFound && deviceName && OVR_strcmp(item->Name, deviceName) == 0)
- {
- deviceFound = true;
-
- for (JSON* deviceItem = item->GetFirstItem(); deviceItem;
- deviceItem = item->GetNextItem(deviceItem))
- {
- profile->ParseProperty(deviceItem->Name, deviceItem->Value);
- }
- }
- }
- }
- }
-
- // Add the new profile
- ProfileCache.PushBack(profile);
- }
- }
-
- CacheDevice = device;
+ }
+
+ const Json::Value & profiles = root[KEY_PROFILES];
+ Json::Value::const_iterator itr;
+ for (itr = profiles.begin(); itr != profiles.end(); ++itr) {
+ String profileName = itr.memberName();
+ Profile * profile = new Profile(profileName);
+ ProfileLoader::loadProfile(*profile, root[profileName]);
+ ProfileCache.PushBack(profile);
+ }
+
+ if (!root.isMember(KEY_CURRENT_PROFILE)) {
+ LogError("Missing current profile");
+ } else {
+ DefaultProfile = root[KEY_CURRENT_PROFILE].asCString();
+ }
+ }
+ break;
+
+ default:
+ LogError("Usupported profile version %d", major);
+ return; // don't parse the file on unsupported major version number
+ }
+ Loaded = true;
}
-
// Serializes the profiles to disk.
void ProfileManager::SaveCache()
{
- String path = GetProfilePath(true);
-
Lock::Locker lockScope(&ProfileLock);
-
- Ptr<JSON> oldroot = *JSON::Load(path);
- if (oldroot)
+ // Limit scope of reader
+ Json::Value root;
+ String path = GetProfilePath(false);
+ if (!parseJsonFile(path, root))
{
- if (oldroot->GetItemCount() >= 3)
- {
- JSON* item0 = oldroot->GetFirstItem();
- JSON* item1 = oldroot->GetNextItem(item0);
- oldroot->GetNextItem(item1);
-
- if (item0->Name == "Oculus Profile Version")
- {
- int major = atoi(item0->Value.ToCStr());
- if (major > MAX_PROFILE_MAJOR_VERSION)
- oldroot.Clear(); // don't use the file on unsupported major version number
- }
- else
- {
- oldroot.Clear();
- }
- }
- else
- {
- oldroot.Clear();
- }
+ root = Json::Value();
}
-
- // Create a new json root
- Ptr<JSON> root = *JSON::CreateObject();
- root->AddNumberItem("Oculus Profile Version", PROFILE_VERSION);
- root->AddStringItem("CurrentProfile", DefaultProfile);
- root->AddNumberItem("ProfileCount", (double) ProfileCache.GetSize());
-
- // Generate a JSON subtree for each profile
- for (unsigned int i=0; i<ProfileCache.GetSize(); i++)
- {
- Profile* profile = ProfileCache[i];
-
- // Write the base profile information
- JSON* json_profile = JSON::CreateObject();
- json_profile->Name = "Profile";
- json_profile->AddStringItem("Name", profile->Name);
- const char* gender;
- switch (profile->GetGender())
- {
- case Profile::Gender_Male: gender = "Male"; break;
- case Profile::Gender_Female: gender = "Female"; break;
- default: gender = "Unspecified";
- }
- json_profile->AddStringItem("Gender", gender);
- json_profile->AddNumberItem("PlayerHeight", profile->PlayerHeight);
- json_profile->AddNumberItem("IPD", profile->IPD);
-
- char* device_name = NULL;
- // Create a device-specific subtree for the cached device
- if (profile->Type == Profile_RiftDK1)
- {
- device_name = "RiftDK1";
-
- RiftDK1Profile* rift = (RiftDK1Profile*)profile;
- JSON* json_rift = JSON::CreateObject();
- json_profile->AddItem(device_name, json_rift);
-
- const char* eyecup = "A";
- switch (rift->EyeCups)
- {
- case EyeCup_A: eyecup = "A"; break;
- case EyeCup_B: eyecup = "B"; break;
- case EyeCup_C: eyecup = "C"; break;
- }
- json_rift->AddStringItem("EyeCup", eyecup);
- json_rift->AddNumberItem("LL", rift->LL);
- json_rift->AddNumberItem("LR", rift->LR);
- json_rift->AddNumberItem("RL", rift->RL);
- json_rift->AddNumberItem("RR", rift->RR);
- }
- else if (profile->Type == Profile_RiftDKHD)
- {
- device_name = "RiftDKHD";
-
- RiftDKHDProfile* rift = (RiftDKHDProfile*)profile;
- JSON* json_rift = JSON::CreateObject();
- json_profile->AddItem(device_name, json_rift);
-
- const char* eyecup = "A";
- switch (rift->EyeCups)
- {
- case EyeCup_A: eyecup = "A"; break;
- case EyeCup_B: eyecup = "B"; break;
- case EyeCup_C: eyecup = "C"; break;
- }
- json_rift->AddStringItem("EyeCup", eyecup);
- //json_rift->AddNumberItem("LL", rift->LL);
- //json_rift->AddNumberItem("LR", rift->LR);
- //json_rift->AddNumberItem("RL", rift->RL);
- //json_rift->AddNumberItem("RR", rift->RR);
- }
-
- // There may be multiple devices stored per user, but only a single
- // device is represented by this root. We don't want to overwrite
- // the other devices so we need to examine the older root
- // and merge previous devices into new json root
- if (oldroot)
- {
- JSON* old_profile = oldroot->GetFirstItem();
- while (old_profile)
- {
- if (old_profile->Name == "Profile")
- {
- JSON* profile_name = old_profile->GetItemByName("Name");
- if (profile_name && OVR_strcmp(profile->Name, profile_name->Value) == 0)
- { // Now that we found the user in the older root, add all the
- // object children to the new root - except for the one for the
- // current device
- JSON* old_item = old_profile->GetFirstItem();
- while (old_item)
- {
- if (old_item->Type == JSON_Object
- && (device_name == NULL || OVR_strcmp(old_item->Name, device_name) != 0))
- {
- JSON* old_device = old_item;
- old_item = old_profile->GetNextItem(old_item);
-
- // remove the node from the older root to avoid multiple reference
- old_device->RemoveNode();
- // add the node pointer to the new root
- json_profile->AddItem(old_device->Name, old_device);
- }
- else
- {
- old_item = old_profile->GetNextItem(old_item);
- }
- }
-
- break;
- }
- }
-
- old_profile = oldroot->GetNextItem(old_profile);
- }
- }
- // Add the completed user profile to the new root
- root->AddItem("Profile", json_profile);
+ // If the file is V1, we don't want to preserve it.
+ // Technically, we could make an effort to preserve it but explcitly
+ // remove the V1 tokens we don't use in V2, but that's a bunch of
+ // effort that will likely serve no useful purpose. Since the file
+ // isn't valid JSON if it has multiple profiles, I doubt anyone is
+ // extending it yet
+ if (root.isMember(KEY_PROFILE_VERSION) &&
+ PROFILE_VERSION != root[KEY_PROFILE_VERSION].asInt()) {
+ root = Json::Value();
}
- // Save the profile to disk
- root->Save(path);
-}
-
-// Returns the number of stored profiles for this device type
-int ProfileManager::GetProfileCount(ProfileType device)
-{
- Lock::Locker lockScope(&ProfileLock);
-
- if (CacheDevice == Profile_Unknown)
- LoadCache(device);
-
- return (int)ProfileCache.GetSize();
-}
-
-// Returns the profile name of a specific profile in the list. The returned
-// memory is locally allocated and should not be stored or deleted. Returns NULL
-// if the index is invalid
-const char* ProfileManager::GetProfileName(ProfileType device, unsigned int index)
-{
- Lock::Locker lockScope(&ProfileLock);
-
- if (CacheDevice == Profile_Unknown)
- LoadCache(device);
+ root[KEY_PROFILE_VERSION] = PROFILE_VERSION;
+ if (!DefaultProfile.IsEmpty()) {
+ root[KEY_CURRENT_PROFILE] = DefaultProfile.ToCStr();
+ }
- if (index < ProfileCache.GetSize())
- {
- Profile* profile = ProfileCache[index];
- OVR_strcpy(NameBuff, Profile::MaxNameLen, profile->Name);
- return NameBuff;
+ // Generate a JSON object of 'profile name' to 'profile data'
+ Json::Value & profiles = root[KEY_PROFILES];
+ for (unsigned int i=0; i<ProfileCache.GetSize(); i++) {
+ const Profile * profile = ProfileCache[i];
+ ProfileLoader::writeProfile(profiles, *profile);
}
- else
+
{
- return NULL;
+ Json::StyledWriter writer;
+ std::string output = writer.write( root );
+ SysFile f;
+ if (!f.Open(path, File::Open_Write | File::Open_Create | File::Open_Truncate, File::Mode_Write)) {
+ LogError("Unable to open %s for writing", path.ToCStr());
+ return;
+ }
+ int written = f.Write((const unsigned char*)output.c_str(),
+ output.length());
+ if (written != output.length()) {
+ LogError("Short write, only %d of %d bytes written",
+ written, (int)output.length());
+ }
+ f.Close();
}
}
-bool ProfileManager::HasProfile(ProfileType device, const char* name)
+// Returns the number of stored profiles for this device type
+unsigned int ProfileManager::GetProfileCount()
{
Lock::Locker lockScope(&ProfileLock);
-
- if (CacheDevice == Profile_Unknown)
- LoadCache(device);
-
- for (unsigned i = 0; i< ProfileCache.GetSize(); i++)
- {
- if (ProfileCache[i] && OVR_strcmp(ProfileCache[i]->Name, name) == 0)
- return true;
- }
- return false;
+ return ProfileCache.GetSize();
}
-
-// Returns a specific profile object in the list. The returned memory should be
-// encapsulated in a Ptr<> object or released after use. Returns NULL if the index
-// is invalid
-Profile* ProfileManager::LoadProfile(ProfileType device, unsigned int index)
+bool ProfileManager::HasProfile(const char* name)
{
- Lock::Locker lockScope(&ProfileLock);
-
- if (CacheDevice == Profile_Unknown)
- LoadCache(device);
-
- if (index < ProfileCache.GetSize())
- {
- Profile* profile = ProfileCache[index];
- return profile->Clone();
- }
- else
- {
- return NULL;
- }
+ return NULL != ProfileCache.at(name);
}
-// Returns a profile object for a particular device and user name. The returned
-// memory should be encapsulated in a Ptr<> object or released after use. Returns
+// Returns a profile object for a particular device and user name. The returned
+// memory should be encapsulated in a Ptr<> object or released after use. Returns
// NULL if the profile is not found
-Profile* ProfileManager::LoadProfile(ProfileType device, const char* user)
+Profile* ProfileManager::LoadProfile(const char* user)
{
+ // Maybe 'null' should be interpreted as 'return the default?'
if (user == NULL)
return NULL;
Lock::Locker lockScope(&ProfileLock);
-
- if (CacheDevice == Profile_Unknown)
- LoadCache(device);
-
- for (unsigned int i=0; i<ProfileCache.GetSize(); i++)
- {
- if (OVR_strcmp(user, ProfileCache[i]->Name) == 0)
- { // Found the requested user profile
- Profile* profile = ProfileCache[i];
- return profile->Clone();
- }
+ LoadCache();
+ Profile * result = ProfileCache.at(user);
+ if (!result) {
+ return NULL;
}
-
- return NULL;
+ // Never give the caller memory that we ourselves are managing.
+ return result->Clone();
}
// Returns a profile with all system default values
-Profile* ProfileManager::GetDeviceDefaultProfile(ProfileType device)
+Profile* ProfileManager::GetDefaultProfile()
{
- const char* device_name = NULL;
- return CreateProfileObject("default", device, &device_name);
+ return new Profile("default");
}
// Returns the name of the profile that is marked as the current default user.
-const char* ProfileManager::GetDefaultProfileName(ProfileType device)
+const char* ProfileManager::GetDefaultProfileName()
{
Lock::Locker lockScope(&ProfileLock);
-
- if (CacheDevice == Profile_Unknown)
- LoadCache(device);
+ LoadCache();
if (ProfileCache.GetSize() > 0)
{
- OVR_strcpy(NameBuff, Profile::MaxNameLen, DefaultProfile);
+ OVR_strcpy(NameBuff, 32, DefaultProfile);
return NameBuff;
}
else
@@ -558,14 +607,12 @@ const char* ProfileManager::GetDefaultProfileName(ProfileType device)
}
}
+
// Marks a particular user as the current default user.
-bool ProfileManager::SetDefaultProfileName(ProfileType device, const char* name)
-{
+bool ProfileManager::SetDefaultProfileName(const char* name) {
Lock::Locker lockScope(&ProfileLock);
-
- if (CacheDevice == Profile_Unknown)
- LoadCache(device);
-// TODO: I should verify that the user is valid
+ LoadCache();
+ // TODO: I should verify that the user is valid
if (ProfileCache.GetSize() > 0)
{
DefaultProfile = name;
@@ -581,132 +628,67 @@ bool ProfileManager::SetDefaultProfileName(ProfileType device, const char* name)
// Saves a new or existing profile. Returns true on success or false on an
// invalid or failed save.
-bool ProfileManager::Save(const Profile* profile)
+bool ProfileManager::Save(const Profile * profile)
{
- Lock::Locker lockScope(&ProfileLock);
+ if (NULL == profile) {
+ return false;
+ }
if (OVR_strcmp(profile->Name, "default") == 0)
return false; // don't save a default profile
- // TODO: I should also verify that this profile type matches the current cache
- if (CacheDevice == Profile_Unknown)
- LoadCache(profile->Type);
+ Lock::Locker lockScope(&ProfileLock);
+ LoadCache();
// Look for the pre-existence of this profile
- bool added = false;
- for (unsigned int i=0; i<ProfileCache.GetSize(); i++)
- {
- int compare = OVR_strcmp(profile->Name, ProfileCache[i]->Name);
-
- if (compare == 0)
- {
- // TODO: I should do a proper field comparison to avoid unnecessary
- // overwrites and file saves
-
- // Replace the previous instance with the new profile
- ProfileCache[i] = *profile->Clone();
- added = true;
- Changed = true;
- break;
- }
- }
-
- if (!added)
- {
- ProfileCache.PushBack(*profile->Clone());
- if (ProfileCache.GetSize() == 1)
- CacheDevice = profile->Type;
+ int index = ProfileCache.IndexOf(profile->Name);
+ if (Map::npos == index) {
+ // TODO: I should do a proper field comparison to avoid unnecessary
+ // overwrites and file saves
- Changed = true;
+ // Replace the previous instance with the new profile
+ ProfileCache[index] = profile->Clone();
+ } else {
+ ProfileCache.PushBack(profile->Clone());
}
-
+ Changed = true;
return true;
}
// Removes an existing profile. Returns true if the profile was found and deleted
// and returns false otherwise.
-bool ProfileManager::Delete(const Profile* profile)
+bool ProfileManager::Delete(const Profile * profile)
{
+ if (NULL == profile) {
+ return false;
+ }
Lock::Locker lockScope(&ProfileLock);
-
- if (OVR_strcmp(profile->Name, "default") == 0)
- return false; // don't delete a default profile
-
- if (CacheDevice == Profile_Unknown)
- LoadCache(profile->Type);
-
- // Look for the existence of this profile
- for (unsigned int i=0; i<ProfileCache.GetSize(); i++)
- {
- if (OVR_strcmp(profile->Name, ProfileCache[i]->Name) == 0)
- {
- if (OVR_strcmp(profile->Name, DefaultProfile) == 0)
- DefaultProfile.Clear();
-
- ProfileCache.RemoveAt(i);
- Changed = true;
- return true;
- }
+ int index = ProfileCache.IndexOf(profile->Name);
+ if (Map::npos == index) {
+ return false;
}
-
- return false;
+ ProfileCache.RemoveAt(index);
+ return true;
}
-
-
//-----------------------------------------------------------------------------
// ***** Profile
-Profile::Profile(ProfileType device, const char* name)
+Profile::Profile(const char* name)
{
- Type = device;
Gender = Gender_Unspecified;
- PlayerHeight = 1.778f; // 5'10" inch man
- IPD = 0.064f;
- OVR_strcpy(Name, MaxNameLen, name);
-}
-
-
-bool Profile::ParseProperty(const char* prop, const char* sval)
-{
- if (OVR_strcmp(prop, "Name") == 0)
- {
- OVR_strcpy(Name, MaxNameLen, sval);
- return true;
- }
- else if (OVR_strcmp(prop, "Gender") == 0)
- {
- if (OVR_strcmp(sval, "Male") == 0)
- Gender = Gender_Male;
- else if (OVR_strcmp(sval, "Female") == 0)
- Gender = Gender_Female;
- else
- Gender = Gender_Unspecified;
-
- return true;
- }
- else if (OVR_strcmp(prop, "PlayerHeight") == 0)
- {
- PlayerHeight = (float)atof(sval);
- return true;
- }
- else if (OVR_strcmp(prop, "IPD") == 0)
- {
- IPD = (float)atof(sval);
- return true;
- }
-
- return false;
+ PlayerHeight = DEFAULT_HEIGHT;
+ IPD = DEFAULT_IPD;
+ Name = name;
}
-
// Computes the eye height from the metric head height
-float Profile::GetEyeHeight()
+float Profile::GetEyeHeight() const
{
const float EYE_TO_HEADTOP_RATIO = 0.44538f;
const float MALE_AVG_HEAD_HEIGHT = 0.232f;
const float FEMALE_AVG_HEAD_HEIGHT = 0.218f;
-
+
// compute distance from top of skull to the eye
float head_height;
if (Gender == Gender_Female)
@@ -720,107 +702,4 @@ float Profile::GetEyeHeight()
return eye_height;
}
-//-----------------------------------------------------------------------------
-// ***** HMDProfile
-
-HMDProfile::HMDProfile(ProfileType type, const char* name) : Profile(type, name)
-{
- LL = 0;
- LR = 0;
- RL = 0;
- RR = 0;
-}
-
-bool HMDProfile::ParseProperty(const char* prop, const char* sval)
-{
- if (OVR_strcmp(prop, "LL") == 0)
- {
- LL = atoi(sval);
- return true;
- }
- else if (OVR_strcmp(prop, "LR") == 0)
- {
- LR = atoi(sval);
- return true;
- }
- else if (OVR_strcmp(prop, "RL") == 0)
- {
- RL = atoi(sval);
- return true;
- }
- else if (OVR_strcmp(prop, "RR") == 0)
- {
- RR = atoi(sval);
- return true;
- }
-
- return Profile::ParseProperty(prop, sval);
-}
-
-Profile* HMDProfile::Clone() const
-{
- HMDProfile* profile = new HMDProfile(*this);
- return profile;
-}
-
-//-----------------------------------------------------------------------------
-// ***** RiftDK1Profile
-
-RiftDK1Profile::RiftDK1Profile(const char* name) : HMDProfile(Profile_RiftDK1, name)
-{
- EyeCups = EyeCup_A;
-}
-
-bool RiftDK1Profile::ParseProperty(const char* prop, const char* sval)
-{
- if (OVR_strcmp(prop, "EyeCup") == 0)
- {
- switch (sval[0])
- {
- case 'C': EyeCups = EyeCup_C; break;
- case 'B': EyeCups = EyeCup_B; break;
- default: EyeCups = EyeCup_A; break;
- }
- return true;
- }
-
- return HMDProfile::ParseProperty(prop, sval);
-}
-
-Profile* RiftDK1Profile::Clone() const
-{
- RiftDK1Profile* profile = new RiftDK1Profile(*this);
- return profile;
-}
-
-//-----------------------------------------------------------------------------
-// ***** RiftDKHDProfile
-
-RiftDKHDProfile::RiftDKHDProfile(const char* name) : HMDProfile(Profile_RiftDKHD, name)
-{
- EyeCups = EyeCup_A;
-}
-
-bool RiftDKHDProfile::ParseProperty(const char* prop, const char* sval)
-{
- if (OVR_strcmp(prop, "EyeCup") == 0)
- {
- switch (sval[0])
- {
- case 'C': EyeCups = EyeCup_C; break;
- case 'B': EyeCups = EyeCup_B; break;
- default: EyeCups = EyeCup_A; break;
- }
- return true;
- }
-
- return HMDProfile::ParseProperty(prop, sval);
-}
-
-Profile* RiftDKHDProfile::Clone() const
-{
- RiftDKHDProfile* profile = new RiftDKHDProfile(*this);
- return profile;
-}
-
} // OVR