aboutsummaryrefslogtreecommitdiffstats
path: root/LibOVR/Src/OVR_Profile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'LibOVR/Src/OVR_Profile.cpp')
-rw-r--r--LibOVR/Src/OVR_Profile.cpp1517
1 files changed, 1517 insertions, 0 deletions
diff --git a/LibOVR/Src/OVR_Profile.cpp b/LibOVR/Src/OVR_Profile.cpp
new file mode 100644
index 0000000..4844c29
--- /dev/null
+++ b/LibOVR/Src/OVR_Profile.cpp
@@ -0,0 +1,1517 @@
+/************************************************************************************
+
+PublicHeader: None
+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
+ 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.
+
+Copyright : Copyright 2014 Oculus VR, Inc. All Rights reserved.
+
+Licensed under the Oculus VR Rift SDK License Version 3.1 (the "License");
+you may not use the Oculus VR Rift SDK except in compliance with the License,
+which is provided at the time of installation or download, or which
+otherwise accompanies this software in either electronic or hard copy form.
+
+You may obtain a copy of the License at
+
+http://www.oculusvr.com/licenses/LICENSE-3.1
+
+Unless required by applicable law or agreed to in writing, the Oculus VR SDK
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+************************************************************************************/
+
+#include "OVR_Profile.h"
+#include "OVR_Device.h"
+#include "OVR_JSON.h"
+#include "Kernel/OVR_Types.h"
+#include "Kernel/OVR_SysFile.h"
+#include "Kernel/OVR_Allocator.h"
+#include "Kernel/OVR_Array.h"
+
+#ifdef OVR_OS_WIN32
+#include <Shlobj.h>
+#else
+#include <dirent.h>
+#include <sys/stat.h>
+
+#ifdef OVR_OS_LINUX
+#include <unistd.h>
+#include <pwd.h>
+#endif
+
+#endif
+
+
+#define PROFILE_VERSION 2.0
+#define MAX_PROFILE_MAJOR_VERSION 2
+#define MAX_DEVICE_PROFILE_MAJOR_VERSION 1
+
+namespace OVR {
+
+//-----------------------------------------------------------------------------
+// Returns the pathname of the JSON file containing the stored profiles
+String GetBaseOVRPath(bool create_dir)
+{
+ String path;
+
+#if defined(OVR_OS_WIN32)
+
+ TCHAR data_path[MAX_PATH];
+ SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, NULL, 0, data_path);
+ path = String(data_path);
+
+ path += "/Oculus";
+
+ if (create_dir)
+ { // Create the Oculus directory if it doesn't exist
+ WCHAR wpath[128];
+ OVR::UTF8Util::DecodeString(wpath, path.ToCStr());
+
+ 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");
+ path = home;
+ path += "/Library/Preferences/Oculus";
+
+ if (create_dir)
+ { // Create the Oculus directory if it doesn't exist
+ DIR* dir = opendir(path);
+ if (dir == NULL)
+ {
+ mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO);
+ }
+ else
+ {
+ closedir(dir);
+ }
+ }
+
+#else
+
+ passwd* pwd = getpwuid(getuid());
+ const char* home = pwd->pw_dir;
+ path = home;
+ path += "/.config/Oculus";
+
+ if (create_dir)
+ { // Create the Oculus directory if it doesn't exist
+ DIR* dir = opendir(path);
+ if (dir == NULL)
+ {
+ mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO);
+ }
+ else
+ {
+ closedir(dir);
+ }
+ }
+
+#endif
+
+ return path;
+}
+
+String ProfileManager::GetProfilePath(bool create_dir)
+{
+ String path = GetBaseOVRPath(create_dir);
+ path += "/ProfileDB.json";
+ return path;
+}
+
+bool ProfileManager::GetDeviceTags(const DeviceBase* device, String& product, String& serial)
+{
+ product = "";
+ serial = "";
+
+ if (device && device->GetType() == Device_HMD)
+ {
+ HMDDevice* hmd = (HMDDevice*)device;
+
+ Ptr<SensorDevice> sensor = *(hmd->GetSensor());
+ if (sensor)
+ {
+ SensorInfo sinfo;
+ sensor->GetDeviceInfo(&sinfo);
+ serial = sinfo.SerialNumber; // get the serial number
+
+ // Derive the product tag from the HMD product name
+ HMDInfo hmdinfo;
+ hmd->GetDeviceInfo(&hmdinfo);
+
+ const char* product_name = NULL;
+
+ // If the HMD is unrecognized then use the name stamped into the
+ // sensor firmware
+ if (hmdinfo.HmdType == HmdType_None || hmdinfo.HmdType == HmdType_Unknown)
+ product_name = sinfo.ProductName.ToCStr();
+ else
+ product_name = hmdinfo.ProductName.ToCStr();
+
+ // First strip off "Oculus"
+ const char* oculus = strstr(product_name, "Oculus ");
+ if (oculus)
+ product_name = oculus + OVR_strlen("Oculus ");
+ // And remove spaces from the name
+ for (const char* s=product_name; *s != 0; s++)
+ {
+ if (*s != ' ')
+ product.AppendChar(*s);
+ }
+ }
+ }
+
+ return (!product.IsEmpty() && !serial.IsEmpty());
+}
+
+static JSON* FindTaggedData(JSON* data, const char** tag_names, const char** qtags, int num_qtags)
+{
+ if (data == NULL || !(data->Name == "TaggedData") || data->Type != JSON_Array)
+ return NULL;
+
+ JSON* tagged_item = data->GetFirstItem();
+ while (tagged_item)
+ {
+ JSON* tags = tagged_item->GetItemByName("tags");
+ if (tags->Type == JSON_Array && num_qtags == tags->GetArraySize())
+ { // Check for a full tag match on each item
+ int num_matches = 0;
+
+ for (int k=0; k<num_qtags; k++)
+ {
+ JSON* tag = tags->GetFirstItem();
+ while (tag)
+ {
+ JSON* tagval = tag->GetFirstItem();
+ if (tagval && tagval->Name == tag_names[k])
+ {
+ if (tagval->Value == qtags[k])
+ num_matches++;
+ break;
+ }
+ tag = tags->GetNextItem(tag);
+ }
+ }
+
+ // if all tags were matched then copy the values into this Profile
+ if (num_matches == num_qtags)
+ {
+ JSON* vals = tagged_item->GetItemByName("vals");
+ return vals;
+ }
+ }
+
+ tagged_item = data->GetNextItem(tagged_item);
+ }
+
+ return NULL;
+}
+
+static void FilterTaggedData(JSON* data, const char* tag_name, const char* qtag, Array<JSON*>& items)
+{
+ if (data == NULL || !(data->Name == "TaggedData") || data->Type != JSON_Array)
+ return;
+
+ JSON* tagged_item = data->GetFirstItem();
+ while (tagged_item)
+ {
+ JSON* tags = tagged_item->GetItemByName("tags");
+ if (tags->Type == JSON_Array)
+ { // Check for a tag match on the requested tag
+
+ JSON* tag = tags->GetFirstItem();
+ while (tag)
+ {
+ JSON* tagval = tag->GetFirstItem();
+ if (tagval && tagval->Name == tag_name)
+ {
+ if (tagval->Value == qtag)
+ { // Add this item to the output list
+ items.PushBack(tagged_item);
+ }
+ break;
+ }
+ tag = tags->GetNextItem(tag);
+ }
+ }
+
+ tagged_item = data->GetNextItem(tagged_item);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// ***** ProfileManager
+
+ProfileManager::ProfileManager()
+{
+ Changed = false;
+}
+
+ProfileManager::~ProfileManager()
+{
+ ClearCache();
+}
+
+ProfileManager* ProfileManager::Create()
+{
+ return new ProfileManager();
+}
+
+// Clear the local profile cache
+void ProfileManager::ClearCache()
+{
+ Lock::Locker lockScope(&ProfileLock);
+ //ProfileCache.Clear();
+ if (ProfileCache)
+ {
+ //ProfileCache->Release();
+ ProfileCache = NULL;
+ }
+ Changed = false;
+}
+
+// Returns a profile with all system default values
+Profile* ProfileManager::GetDefaultProfile(const DeviceBase* device)
+{
+ // In the absence of any data, set some reasonable profile defaults.
+ // However, this is not future proof and developers should still
+ // provide reasonable default values for queried fields.
+ Profile* profile = CreateProfile();
+ profile->SetValue(OVR_KEY_USER, "default");
+ profile->SetValue(OVR_KEY_NAME, "Default");
+ profile->SetValue(OVR_KEY_GENDER, OVR_DEFAULT_GENDER);
+ profile->SetFloatValue(OVR_KEY_PLAYER_HEIGHT, OVR_DEFAULT_PLAYER_HEIGHT);
+ profile->SetFloatValue(OVR_KEY_EYE_HEIGHT, 1.675f);
+ profile->SetFloatValue(OVR_KEY_IPD, OVR_DEFAULT_IPD);
+ float dist[2] = {OVR_DEFAULT_NECK_TO_EYE_HORIZONTAL, OVR_DEFAULT_NECK_TO_EYE_VERTICAL};
+ profile->SetFloatValues(OVR_KEY_NECK_TO_EYE_DISTANCE, dist, 2);
+ //profile->SetFloatValue(OVR_KEY_NECK_TO_EYE_VERTICAL, 0.12f);
+
+ // TODO: Provide device specific defaults
+ OVR_UNUSED(device);
+
+ // DK1 default
+ //profile->SetValue("EyeCup", "A");
+
+ return profile;
+}
+
+// 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(bool create)
+{
+ Lock::Locker lockScope(&ProfileLock);
+
+ ClearCache();
+
+ String path = GetProfilePath(false);
+
+ Ptr<JSON> root = *JSON::Load(path);
+ if (root == NULL)
+ {
+ path = GetBaseOVRPath(false) + "/Profiles.json"; // look for legacy profile
+ root = *JSON::Load(path);
+
+ if (root == NULL)
+ {
+ if (create)
+ { // Generate a skeleton profile database
+ root = *JSON::CreateObject();
+ root->AddNumberItem("Oculus Profile Version", 2.0);
+ root->AddItem("Users", JSON::CreateArray());
+ root->AddItem("TaggedData", JSON::CreateArray());
+ ProfileCache = root;
+ }
+
+ return;
+ }
+
+ // Verify the legacy version
+ JSON* version_item = root->GetFirstItem();
+ if (version_item->Name == "Oculus Profile Version")
+ {
+ int major = atoi(version_item->Value.ToCStr());
+ if (major != 1)
+ return; // don't use the file on unsupported major version number
+ }
+ else
+ {
+ return; // invalid file
+ }
+
+ // Convert the legacy format to the new database format
+ LoadV1Profiles(root);
+ }
+ else
+ {
+ // Verify the file format and version
+ JSON* version_item = root->GetFirstItem();
+ if (version_item->Name == "Oculus Profile Version")
+ {
+ int major = atoi(version_item->Value.ToCStr());
+ if (major != 2)
+ return; // don't use the file on unsupported major version number
+ }
+ else
+ {
+ return; // invalid file
+ }
+
+ ProfileCache = root; // store the database contents for traversal
+ }
+}
+
+void ProfileManager::LoadV1Profiles(JSON* v1)
+{
+ JSON* item0 = v1->GetFirstItem();
+ JSON* item1 = v1->GetNextItem(item0);
+ JSON* item2 = v1->GetNextItem(item1);
+
+ // Create the new profile database
+ Ptr<JSON> root = *JSON::CreateObject();
+ root->AddNumberItem("Oculus Profile Version", 2.0);
+ root->AddItem("Users", JSON::CreateArray());
+ root->AddItem("TaggedData", JSON::CreateArray());
+ ProfileCache = root;
+
+ const char* default_dk1_user = 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
+ }
+
+ // Read the user profile fields
+ if (CreateUser(profileName, profileName))
+ {
+ const char* tag_names[2] = {"User", "Product"};
+ const char* tags[2];
+ tags[0] = profileName;
+
+ Ptr<Profile> user_profile = *CreateProfile();
+ user_profile->SetValue(OVR_KEY_NAME, profileName);
+
+ float neckeye[2] = { 0, 0 };
+
+ item = profileItem->GetNextItem(item);
+ while (item)
+ {
+ if (item->Type != JSON_Object)
+ {
+ if (item->Name == OVR_KEY_PLAYER_HEIGHT)
+ { // Add an explicit eye height
+
+ }
+ if (item->Name == "NeckEyeHori")
+ neckeye[0] = (float)item->dValue;
+ else if (item->Name == "NeckEyeVert")
+ neckeye[1] = (float)item->dValue;
+ else
+ user_profile->SetValue(item);
+ }
+ else
+ {
+ // Add the user/device tag values
+ const char* device_name = item->Name.ToCStr();
+ Ptr<Profile> device_profile = *CreateProfile();
+
+ JSON* device_item = item->GetFirstItem();
+ while (device_item)
+ {
+ device_profile->SetValue(device_item);
+ device_item = item->GetNextItem(device_item);
+ }
+
+ tags[1] = device_name;
+ SetTaggedProfile(tag_names, tags, 2, device_profile);
+ }
+
+ item = profileItem->GetNextItem(item);
+ }
+
+ // Add an explicit eye-height field
+ float player_height = user_profile->GetFloatValue(OVR_KEY_PLAYER_HEIGHT,
+ OVR_DEFAULT_PLAYER_HEIGHT);
+ if (player_height > 0)
+ {
+ char gender[16];
+ user_profile->GetValue(OVR_KEY_GENDER, gender, 16);
+
+ 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 (OVR_strcmp(gender, "Female") == 0)
+ head_height = FEMALE_AVG_HEAD_HEIGHT;
+ else
+ head_height = MALE_AVG_HEAD_HEIGHT;
+
+ float skull = EYE_TO_HEADTOP_RATIO * head_height;
+ float eye_height = player_height - skull;
+
+ user_profile->SetFloatValue(OVR_KEY_EYE_HEIGHT, eye_height);
+ }
+
+ // Convert NeckEye values to an array
+ if (neckeye[0] > 0 && neckeye[1] > 0)
+ user_profile->SetFloatValues(OVR_KEY_NECK_TO_EYE_DISTANCE, neckeye, 2);
+
+ // Add the user tag values
+ SetTaggedProfile(tag_names, tags, 1, user_profile);
+ }
+ }
+ }
+
+ // since V1 profiles were only for DK1, the assign the user to all DK1's
+ const char* tag_names[1] = { "Product" };
+ const char* tags[1] = { "RiftDK1" };
+ Ptr<Profile> product_profile = *CreateProfile();
+ product_profile->SetValue("DefaultUser", default_dk1_user);
+ SetTaggedProfile(tag_names, tags, 1, product_profile);
+}
+
+// Returns the number of stored profiles for this device type
+int ProfileManager::GetUserCount()
+{
+ Lock::Locker lockScope(&ProfileLock);
+
+ if (ProfileCache == NULL)
+ { // Load the cache
+ LoadCache(false);
+ if (ProfileCache == NULL)
+ return 0;
+ }
+
+ JSON* users = ProfileCache->GetItemByName("Users");
+ if (users == NULL)
+ return 0;
+
+ return users->GetItemCount();
+}
+
+bool ProfileManager::CreateUser(const char* user, const char* name)
+{
+ Lock::Locker lockScope(&ProfileLock);
+
+ if (ProfileCache == NULL)
+ { // Load the cache
+ LoadCache(true);
+ if (ProfileCache == NULL)
+ return false;
+ }
+
+ JSON* users = ProfileCache->GetItemByName("Users");
+ if (users == NULL)
+ { // Generate the User section
+ users = JSON::CreateArray();
+ ProfileCache->AddItem("Users", users);
+//TODO: Insert this before the TaggedData
+ }
+
+ // Search for the pre-existence of this user
+ JSON* user_item = users->GetFirstItem();
+ int index = 0;
+ while (user_item)
+ {
+ JSON* userid = user_item->GetItemByName("User");
+ int compare = OVR_strcmp(user, userid->Value);
+ if (compare == 0)
+ { // The user already exists so simply update the fields
+ JSON* name_item = user_item->GetItemByName("Name");
+ if (name_item && OVR_strcmp(name, name_item->Value) != 0)
+ {
+ name_item->Value = name;
+ Changed = true;
+ }
+ return true;
+ }
+ else if (compare < 0)
+ { // A new user should be placed before this item
+ break;
+ }
+
+ user_item = users->GetNextItem(user_item);
+ index++;
+ }
+
+ // Create and fill the user struct
+ JSON* new_user = JSON::CreateObject();
+ new_user->AddStringItem(OVR_KEY_USER, user);
+ new_user->AddStringItem(OVR_KEY_NAME, name);
+ // user_item->AddStringItem("Password", password);
+
+ if (user_item == NULL)
+ users->AddArrayElement(new_user);
+ else
+ users->InsertArrayElement(index, new_user);
+
+ Changed = true;
+ return true;
+}
+
+// Returns the user id of a specific user 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::GetUser(unsigned int index)
+{
+ Lock::Locker lockScope(&ProfileLock);
+
+ if (ProfileCache == NULL)
+ { // Load the cache
+ LoadCache(false);
+ if (ProfileCache == NULL)
+ return NULL;
+ }
+
+ JSON* users = ProfileCache->GetItemByName("Users");
+
+ if (users && index < users->GetItemCount())
+ {
+ JSON* user_item = users->GetItemByIndex(index);
+ if (user_item)
+ {
+ JSON* user = user_item->GetFirstItem();
+ if (user)
+ {
+ JSON* userid = user_item->GetItemByName(OVR_KEY_USER);
+ if (userid)
+ return userid->Value.ToCStr();
+ }
+ }
+ }
+
+
+ return NULL;
+}
+
+bool ProfileManager::RemoveUser(const char* user)
+{
+ Lock::Locker lockScope(&ProfileLock);
+
+ if (ProfileCache == NULL)
+ { // Load the cache
+ LoadCache(false);
+ if (ProfileCache == NULL)
+ return true;
+ }
+
+ JSON* users = ProfileCache->GetItemByName("Users");
+ if (users == NULL)
+ return true;
+
+ // Remove this user from the User table
+ JSON* user_item = users->GetFirstItem();
+ while (user_item)
+ {
+ JSON* userid = user_item->GetItemByName("User");
+ if (OVR_strcmp(user, userid->Value) == 0)
+ { // Delete the user entry
+ user_item->RemoveNode();
+ user_item->Release();
+ Changed = true;
+ break;
+ }
+
+ user_item = users->GetNextItem(user_item);
+ }
+
+ // Now remove all data entries with this user tag
+ JSON* tagged_data = ProfileCache->GetItemByName("TaggedData");
+ Array<JSON*> user_items;
+ FilterTaggedData(tagged_data, "User", user, user_items);
+ for (unsigned int i=0; i<user_items.GetSize(); i++)
+ {
+ user_items[i]->RemoveNode();
+ user_items[i]->Release();
+ Changed = true;
+ }
+
+ return Changed;
+}
+
+Profile* ProfileManager::CreateProfile()
+{
+ Profile* profile = new Profile();
+ return profile;
+}
+
+// Returns the name of the profile that is marked as the current default user.
+const char* ProfileManager::GetDefaultUser(const DeviceBase* device)
+{
+ const char* tag_names[2] = {"Product", "Serial"};
+ const char* tags[2];
+
+ String product;
+ String serial;
+ if (!GetDeviceTags(device, product, serial))
+ return NULL;
+
+ const char* product_str = product.IsEmpty() ? NULL : product.ToCStr();
+ const char* serial_str = serial.IsEmpty() ? NULL : serial.ToCStr();
+
+ if (product_str && serial_str)
+ {
+ tags[0] = product_str;
+ tags[1] = serial_str;
+ // Look for a default user on this specific device
+ Ptr<Profile> p = *GetTaggedProfile(tag_names, tags, 2);
+ if (p == NULL)
+ { // Look for a default user on this product
+ p = *GetTaggedProfile(tag_names, tags, 1);
+ }
+
+ if (p)
+ {
+ const char* user = p->GetValue("DefaultUser");
+ if (user != NULL && user[0] != 0)
+ {
+ TempBuff = user;
+ return TempBuff.ToCStr();
+ }
+ }
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+bool ProfileManager::SetDefaultUser(const DeviceBase* device, const char* user)
+{
+ const char* tag_names[2] = {"Product", "Serial"};
+ const char* tags[2];
+
+ String product;
+ String serial;
+ if (!GetDeviceTags(device, product, serial))
+ return NULL;
+
+ const char* product_str = product.IsEmpty() ? NULL : product.ToCStr();
+ const char* serial_str = serial.IsEmpty() ? NULL : serial.ToCStr();
+
+ if (product_str && serial_str)
+ {
+ tags[0] = product_str;
+ tags[1] = serial_str;
+
+ Ptr<Profile> p = *CreateProfile();
+ p->SetValue("DefaultUser", user);
+ return SetTaggedProfile(tag_names, tags, 2, p);
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+Profile* ProfileManager::GetTaggedProfile(const char** tag_names, const char** tags, int num_tags)
+{
+ Lock::Locker lockScope(&ProfileLock);
+
+ if (ProfileCache == NULL)
+ { // Load the cache
+ LoadCache(false);
+ if (ProfileCache == NULL)
+ return NULL;
+ }
+
+ JSON* tagged_data = ProfileCache->GetItemByName("TaggedData");
+ OVR_ASSERT(tagged_data);
+ if (tagged_data == NULL)
+ return NULL;
+
+ Profile* profile = new Profile();
+
+ JSON* vals = FindTaggedData(tagged_data, tag_names, tags, num_tags);
+ if (vals)
+ {
+ JSON* item = vals->GetFirstItem();
+ while (item)
+ {
+ //printf("Add %s, %s\n", item->Name.ToCStr(), item->Value.ToCStr());
+ //profile->Settings.Set(item->Name, item->Value);
+ profile->SetValue(item);
+ item = vals->GetNextItem(item);
+ }
+
+ return profile;
+ }
+ else
+ {
+ profile->Release();
+ return NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+bool ProfileManager::SetTaggedProfile(const char** tag_names, const char** tags, int num_tags, Profile* profile)
+{
+ Lock::Locker lockScope(&ProfileLock);
+
+ if (ProfileCache == NULL)
+ { // Load the cache
+ LoadCache(true);
+ if (ProfileCache == NULL)
+ return false; // TODO: Generate a new profile DB
+ }
+
+ JSON* tagged_data = ProfileCache->GetItemByName("TaggedData");
+ OVR_ASSERT(tagged_data);
+ if (tagged_data == NULL)
+ return false;
+
+ // Get the cached tagged data section
+ JSON* vals = FindTaggedData(tagged_data, tag_names, tags, num_tags);
+ if (vals == NULL)
+ {
+ JSON* tagged_item = JSON::CreateObject();
+ JSON* taglist = JSON::CreateArray();
+ for (int i=0; i<num_tags; i++)
+ {
+ JSON* k = JSON::CreateObject();
+ k->AddStringItem(tag_names[i], tags[i]);
+ taglist->AddArrayElement(k);
+ }
+
+ vals = JSON::CreateObject();
+
+ tagged_item->AddItem("tags", taglist);
+ tagged_item->AddItem("vals", vals);
+ tagged_data->AddArrayElement(tagged_item);
+ }
+
+ // Now add or update each profile setting in cache
+ for (unsigned int i=0; i<profile->Values.GetSize(); i++)
+ {
+ JSON* value = profile->Values[i];
+
+ bool found = false;
+ JSON* item = vals->GetFirstItem();
+ while (item)
+ {
+ if (value->Name == item->Name)
+ {
+ // Don't allow a pre-existing type to be overridden
+ OVR_ASSERT(value->Type == item->Type);
+
+ if (value->Type == item->Type)
+ { // Check for the same value
+ if (value->Type == JSON_Array)
+ { // Update each array item
+ if (item->GetArraySize() == value->GetArraySize())
+ { // Update each value (assumed to be basic types and not array of objects)
+ JSON* value_element = value->GetFirstItem();
+ JSON* item_element = item->GetFirstItem();
+ while (item_element && value_element)
+ {
+ if (value_element->Type == JSON_String)
+ {
+ if (item_element->Value != value_element->Value)
+ { // Overwrite the changed value and mark for file update
+ item_element->Value = value_element->Value;
+ Changed = true;
+ }
+ }
+ else {
+ if (item_element->dValue != value_element->dValue)
+ { // Overwrite the changed value and mark for file update
+ item_element->dValue = value_element->dValue;
+ Changed = true;
+ }
+ }
+
+ value_element = value->GetNextItem(value_element);
+ item_element = item->GetNextItem(item_element);
+ }
+ }
+ else
+ { // if the array size changed, simply create a new one
+// TODO: Create the new array
+ }
+ }
+ else if (value->Type == JSON_String)
+ {
+ if (item->Value != value->Value)
+ { // Overwrite the changed value and mark for file update
+ item->Value = value->Value;
+ Changed = true;
+ }
+ }
+ else {
+ if (item->dValue != value->dValue)
+ { // Overwrite the changed value and mark for file update
+ item->dValue = value->dValue;
+ Changed = true;
+ }
+ }
+ }
+ else
+ {
+ return false;
+ }
+
+ found = true;
+ break;
+ }
+
+ item = vals->GetNextItem(item);
+ }
+
+ if (!found)
+ { // Add the new value
+ if (value->Type == JSON_String)
+ vals->AddStringItem(value->Name, value->Value);
+ else if (value->Type == JSON_Bool)
+ vals->AddBoolItem(value->Name, (value->dValue != 0));
+ else if (value->Type == JSON_Array)
+ vals->AddItem(value->Name, value->Copy());
+ else
+ vals->AddNumberItem(value->Name, value->dValue);
+
+ Changed = true;
+ }
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+Profile* ProfileManager::GetProfile(const DeviceBase* device, const char* user)
+{
+ Lock::Locker lockScope(&ProfileLock);
+
+ if (ProfileCache == NULL)
+ { // Load the cache
+ LoadCache(false);
+ if (ProfileCache == NULL)
+ return NULL;
+ }
+
+ Profile* profile = new Profile();
+
+ if (device)
+ {
+ if (!profile->LoadDeviceProfile(device) && (user == NULL))
+ {
+ profile->Release();
+ return NULL;
+ }
+ }
+
+ if (user)
+ {
+ String product;
+ String serial;
+ GetDeviceTags(device, product, serial);
+
+ const char* product_str = product.IsEmpty() ? NULL : product.ToCStr();
+ const char* serial_str = serial.IsEmpty() ? NULL : serial.ToCStr();
+
+ if (!profile->LoadProfile(ProfileCache.GetPtr(), user, product_str, serial_str))
+ {
+ profile->Release();
+ return NULL;
+ }
+ }
+
+ return profile;
+}
+
+//-----------------------------------------------------------------------------
+// ***** Profile
+
+Profile::~Profile()
+{
+ ValMap.Clear();
+ for (unsigned int i=0; i<Values.GetSize(); i++)
+ Values[i]->Release();
+
+ Values.Clear();
+}
+
+bool Profile::Close()
+{
+ // TODO:
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+void Profile::CopyItems(JSON* root, String prefix)
+{
+ JSON* item = root->GetFirstItem();
+ while (item)
+ {
+ String item_name;
+ if (prefix.IsEmpty())
+ item_name = item->Name;
+ else
+ item_name = prefix + "." + item->Name;
+
+ if (item->Type == JSON_Object)
+ { // recursively copy the children
+
+ CopyItems(item, item_name);
+ }
+ else
+ {
+ //Settings.Set(item_name, item->Value);
+ SetValue(item);
+ }
+
+ item = root->GetNextItem(item);
+ }
+}
+
+//-----------------------------------------------------------------------------
+bool Profile::LoadDeviceFile(unsigned int device_id, const char* serial)
+{
+ if (serial[0] == 0)
+ return false;
+
+ String path = GetBaseOVRPath(false);
+ path += "/Devices.json";
+
+ // Load the device profiles
+ Ptr<JSON> root = *JSON::Load(path);
+ if (root == NULL)
+ return false;
+
+ // Quick sanity check of the file type and format before we parse it
+ JSON* version = root->GetFirstItem();
+ if (version && version->Name == "Oculus Device Profile Version")
+ {
+ int major = atoi(version->Value.ToCStr());
+ if (major > MAX_DEVICE_PROFILE_MAJOR_VERSION)
+ return false; // don't parse the file on unsupported major version number
+ }
+ else
+ {
+ return false;
+ }
+
+
+ JSON* device = root->GetNextItem(version);
+ while (device)
+ {
+ if (device->Name == "Device")
+ {
+ JSON* product_item = device->GetItemByName("ProductID");
+ JSON* serial_item = device->GetItemByName("Serial");
+ if (product_item && serial_item
+ && (product_item->dValue == device_id) && (serial_item->Value == serial))
+ {
+ // found the entry for this device so recursively copy all the settings to the profile
+ CopyItems(device, "");
+ return true;
+ }
+ }
+
+ device = root->GetNextItem(device);
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+static int BCDByte(unsigned int byte)
+{
+ int digit1 = (byte >> 4) & 0x000f;
+ int digit2 = byte & 0x000f;
+ int decimal = digit1 * 10 + digit2;
+ return decimal;
+}
+
+//-----------------------------------------------------------------------------
+bool Profile::LoadDeviceProfile(const DeviceBase* device)
+{
+ bool success = false;
+ if (device == NULL)
+ return false;
+
+ SensorDevice* sensor = NULL;
+
+ if (device->GetType() == Device_HMD)
+ {
+ // Convert the HMD device to Sensor
+ sensor = ((HMDDevice*)device)->GetSensor();
+ device = sensor;
+ if (device == NULL)
+ return false;
+ }
+
+ if (device->GetType() == Device_Sensor)
+ {
+ SensorDevice* sensor = (SensorDevice*)device;
+
+ SensorInfo sinfo;
+ sensor->GetDeviceInfo(&sinfo);
+
+ int dev_major = BCDByte((sinfo.Version >> 8) & 0x00ff);
+ OVR_UNUSED(dev_major);
+ int dev_minor = BCDByte(sinfo.Version & 0xff);
+
+ if (dev_minor > 18)
+ { // If the firmware supports hardware stored profiles then grab the device profile
+ // from the sensor
+ // TBD: Implement this
+ }
+ else
+ {
+ // Grab the model and serial number from the device and use it to access the device
+ // profile file stored on the local machine
+ success = LoadDeviceFile(sinfo.ProductId, sinfo.SerialNumber);
+ }
+ }
+
+ if (sensor)
+ sensor->Release(); // release the sensor handle
+
+ return success;
+}
+
+//-----------------------------------------------------------------------------
+bool Profile::LoadUser(JSON* root,
+ const char* user,
+ const char* model_name,
+ const char* device_serial)
+{
+ if (user == NULL)
+ return false;
+
+ // For legacy files, convert to old style names
+ //if (model_name && OVR_strcmp(model_name, "Oculus Rift DK1") == 0)
+ // model_name = "RiftDK1";
+
+ bool user_found = false;
+ JSON* data = root->GetItemByName("TaggedData");
+ if (data)
+ {
+ const char* tag_names[3];
+ const char* tags[3];
+ tag_names[0] = "User";
+ tags[0] = user;
+ int num_tags = 1;
+
+ if (model_name)
+ {
+ tag_names[num_tags] = "Product";
+ tags[num_tags] = model_name;
+ num_tags++;
+ }
+
+ if (device_serial)
+ {
+ tag_names[num_tags] = "Serial";
+ tags[num_tags] = device_serial;
+ num_tags++;
+ }
+
+ // Retrieve all tag permutations
+ for (int combos=1; combos<=num_tags; combos++)
+ {
+ for (int i=0; i<(num_tags - combos + 1); i++)
+ {
+ JSON* vals = FindTaggedData(data, tag_names+i, tags+i, combos);
+ if (vals)
+ {
+ if (i==0) // This tag-combination contains a user match
+ user_found = true;
+
+ // Add the values to the Profile. More specialized multi-tag values
+ // will take precedence over and overwrite generalized ones
+ // For example: ("Me","RiftDK1").IPD would overwrite ("Me").IPD
+ JSON* item = vals->GetFirstItem();
+ while (item)
+ {
+ //printf("Add %s, %s\n", item->Name.ToCStr(), item->Value.ToCStr());
+ //Settings.Set(item->Name, item->Value);
+ SetValue(item);
+ item = vals->GetNextItem(item);
+ }
+ }
+ }
+ }
+ }
+
+ if (user_found)
+ SetValue(OVR_KEY_USER, user);
+
+ return user_found;
+}
+
+
+//-----------------------------------------------------------------------------
+bool Profile::LoadProfile(JSON* root,
+ const char* user,
+ const char* device_model,
+ const char* device_serial)
+{
+ if (!LoadUser(root, user, device_model, device_serial))
+ return false;
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+char* Profile::GetValue(const char* key, char* val, int val_length) const
+{
+ JSON* value = NULL;
+ if (ValMap.Get(key, &value))
+ {
+ OVR_strcpy(val, val_length, value->Value.ToCStr());
+ return val;
+ }
+ else
+ {
+ val[0] = 0;
+ return NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+const char* Profile::GetValue(const char* key)
+{
+ // Non-reentrant query. The returned buffer can only be used until the next call
+ // to GetValue()
+ JSON* value = NULL;
+ if (ValMap.Get(key, &value))
+ {
+ TempVal = value->Value;
+ return TempVal.ToCStr();
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+int Profile::GetNumValues(const char* key) const
+{
+ JSON* value = NULL;
+ if (ValMap.Get(key, &value))
+ {
+ if (value->Type == JSON_Array)
+ return value->GetArraySize();
+ else
+ return 1;
+ }
+ else
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+bool Profile::GetBoolValue(const char* key, bool default_val) const
+{
+ JSON* value = NULL;
+ if (ValMap.Get(key, &value) && value->Type == JSON_Bool)
+ return (value->dValue != 0);
+ else
+ return default_val;
+}
+
+//-----------------------------------------------------------------------------
+int Profile::GetIntValue(const char* key, int default_val) const
+{
+ JSON* value = NULL;
+ if (ValMap.Get(key, &value) && value->Type == JSON_Number)
+ return (int)(value->dValue);
+ else
+ return default_val;
+}
+
+//-----------------------------------------------------------------------------
+float Profile::GetFloatValue(const char* key, float default_val) const
+{
+ JSON* value = NULL;
+ if (ValMap.Get(key, &value) && value->Type == JSON_Number)
+ return (float)(value->dValue);
+ else
+ return default_val;
+}
+
+//-----------------------------------------------------------------------------
+int Profile::GetFloatValues(const char* key, float* values, int num_vals) const
+{
+ JSON* value = NULL;
+ if (ValMap.Get(key, &value) && value->Type == JSON_Array)
+ {
+ int val_count = Alg::Min(value->GetArraySize(), num_vals);
+ JSON* item = value->GetFirstItem();
+ int count=0;
+ while (item && count < val_count)
+ {
+ if (item->Type == JSON_Number)
+ values[count] = (float)item->dValue;
+ else
+ break;
+
+ count++;
+ item = value->GetNextItem(item);
+ }
+
+ return count;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+double Profile::GetDoubleValue(const char* key, double default_val) const
+{
+ JSON* value = NULL;
+ if (ValMap.Get(key, &value) && value->Type == JSON_Number)
+ return value->dValue;
+ else
+ return default_val;
+}
+
+//-----------------------------------------------------------------------------
+int Profile::GetDoubleValues(const char* key, double* values, int num_vals) const
+{
+ JSON* value = NULL;
+ if (ValMap.Get(key, &value) && value->Type == JSON_Array)
+ {
+ int val_count = Alg::Min(value->GetArraySize(), num_vals);
+ JSON* item = value->GetFirstItem();
+ int count=0;
+ while (item && count < val_count)
+ {
+ if (item->Type == JSON_Number)
+ values[count] = item->dValue;
+ else
+ break;
+
+ count++;
+ item = value->GetNextItem(item);
+ }
+
+ return count;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void Profile::SetValue(JSON* val)
+{
+ if (val->Type == JSON_Number)
+ SetDoubleValue(val->Name, val->dValue);
+ else if (val->Type == JSON_Bool)
+ SetBoolValue(val->Name, (val->dValue != 0));
+ else if (val->Type == JSON_String)
+ SetValue(val->Name, val->Value);
+ else if (val->Type == JSON_Array)
+ {
+ if (val == NULL)
+ return;
+
+ // Create a copy of the array
+ JSON* value = val->Copy();
+ Values.PushBack(value);
+ ValMap.Set(value->Name, value);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void Profile::SetValue(const char* key, const char* val)
+{
+ if (key == NULL || val == NULL)
+ return;
+
+ JSON* value = NULL;
+ if (ValMap.Get(key, &value))
+ {
+ value->Value = val;
+ }
+ else
+ {
+ value = JSON::CreateString(val);
+ value->Name = key;
+
+ Values.PushBack(value);
+ ValMap.Set(key, value);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void Profile::SetBoolValue(const char* key, bool val)
+{
+ if (key == NULL)
+ return;
+
+ JSON* value = NULL;
+ if (ValMap.Get(key, &value))
+ {
+ value->dValue = val;
+ }
+ else
+ {
+ value = JSON::CreateBool(val);
+ value->Name = key;
+
+ Values.PushBack(value);
+ ValMap.Set(key, value);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void Profile::SetIntValue(const char* key, int val)
+{
+ SetDoubleValue(key, val);
+}
+
+//-----------------------------------------------------------------------------
+void Profile::SetFloatValue(const char* key, float val)
+{
+ SetDoubleValue(key, val);
+}
+
+//-----------------------------------------------------------------------------
+void Profile::SetFloatValues(const char* key, const float* vals, int num_vals)
+{
+ JSON* value = NULL;
+ int val_count = 0;
+ if (ValMap.Get(key, &value))
+ {
+ if (value->Type == JSON_Array)
+ {
+ // truncate the existing array if fewer entries provided
+ int num_existing_vals = value->GetArraySize();
+ for (int i=num_vals; i<num_existing_vals; i++)
+ value->RemoveLast();
+
+ JSON* item = value->GetFirstItem();
+ while (item && val_count < num_vals)
+ {
+ if (item->Type == JSON_Number)
+ item->dValue = vals[val_count];
+
+ item = value->GetNextItem(item);
+ val_count++;
+ }
+ }
+ else
+ {
+ return; // Maybe we should change the data type?
+ }
+ }
+ else
+ {
+ value = JSON::CreateArray();
+ value->Name = key;
+
+ Values.PushBack(value);
+ ValMap.Set(key, value);
+ }
+
+ for (; val_count < num_vals; val_count++)
+ value->AddArrayNumber(vals[val_count]);
+}
+
+//-----------------------------------------------------------------------------
+void Profile::SetDoubleValue(const char* key, double val)
+{
+ JSON* value = NULL;
+ if (ValMap.Get(key, &value))
+ {
+ value->dValue = val;
+ }
+ else
+ {
+ value = JSON::CreateNumber(val);
+ value->Name = key;
+
+ Values.PushBack(value);
+ ValMap.Set(key, value);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void Profile::SetDoubleValues(const char* key, const double* vals, int num_vals)
+{
+ JSON* value = NULL;
+ int val_count = 0;
+ if (ValMap.Get(key, &value))
+ {
+ if (value->Type == JSON_Array)
+ {
+ // truncate the existing array if fewer entries provided
+ int num_existing_vals = value->GetArraySize();
+ for (int i=num_vals; i<num_existing_vals; i++)
+ value->RemoveLast();
+
+ JSON* item = value->GetFirstItem();
+ while (item && val_count < num_vals)
+ {
+ if (item->Type == JSON_Number)
+ item->dValue = vals[val_count];
+
+ item = value->GetNextItem(item);
+ val_count++;
+ }
+ }
+ else
+ {
+ return; // Maybe we should change the data type?
+ }
+ }
+ else
+ {
+ value = JSON::CreateArray();
+ value->Name = key;
+
+ Values.PushBack(value);
+ ValMap.Set(key, value);
+ }
+
+ for (; val_count < num_vals; val_count++)
+ value->AddArrayNumber(vals[val_count]);
+}
+
+} // OVR