diff options
Diffstat (limited to 'LibOVR/Src/OVR_Profile.cpp')
-rw-r--r-- | LibOVR/Src/OVR_Profile.cpp | 1517 |
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 |