/************************************************************************************ 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, LLC All Rights reserved. Licensed under the Oculus VR Rift SDK License Version 3.2 (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.2 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_JSON.h" #include "Kernel/OVR_SysFile.h" #include "Kernel/OVR_Allocator.h" #include "OVR_Stereo.h" #ifdef OVR_OS_WIN32 #define WIN32_LEAN_AND_MEAN #include #include #elif defined(OVR_OS_MS) // Other Microsoft OSs // Nothing, thanks. #else #include #include #ifdef OVR_OS_LINUX #include #include #endif #endif #define PROFILE_VERSION 2.0 #define MAX_PROFILE_MAJOR_VERSION 2 #define MAX_DEVICE_PROFILE_MAJOR_VERSION 1 namespace OVR { //----------------------------------------------------------------------------- // ProfileDeviceKey ProfileDeviceKey::ProfileDeviceKey(const HMDInfo* info) : Valid(false) { if (info) { PrintedSerial = info->PrintedSerial; ProductName = SanitizeProductName(info->ProductName); ProductId = info->ProductId; HmdType = info->HmdType; if (ProductId != 0) { Valid = true; } } else { ProductId = 0; HmdType = HmdType_None; } } String ProfileDeviceKey::SanitizeProductName(String productName) { String result; if (!productName.IsEmpty()) { const char* product_name = 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 != ' ') { result.AppendChar(*s); } } } return result; } //----------------------------------------------------------------------------- // 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_OS) // Other Microsoft OSs // TODO: figure this out. OVR_UNUSED ( create_dir ); path = ""; #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 const char* home = getenv("HOME"); 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() { return BasePath + "/ProfileDB.json"; } 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; kGetFirstItem(); 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& 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 template<> ProfileManager* OVR::SystemSingletonBase::SlowGetInstance() { static OVR::Lock lock; OVR::Lock::Locker locker(&lock); if (!SingletonInstance) SingletonInstance = new ProfileManager(true); return SingletonInstance; } ProfileManager::ProfileManager(bool sys_register) : Changed(false) { // Attempt to get the base path automatically, but this may fail BasePath = GetBaseOVRPath(false); if (sys_register) PushDestroyCallbacks(); } ProfileManager::~ProfileManager() { ClearProfileData(); } void ProfileManager::OnSystemDestroy() { delete this; } // In the service process it is important to set the base path because this cannot be detected automatically void ProfileManager::SetBasePath(String basePath) { if (basePath != BasePath) { BasePath = basePath; LoadCache(false); } } // Clear the local profile cache void ProfileManager::ClearProfileData() { Lock::Locker lockScope(&ProfileLock); ProfileCache.Clear(); Changed = false; } // Serializes the profiles to disk. void ProfileManager::Save() { Lock::Locker lockScope(&ProfileLock); if (ProfileCache == NULL) return; // Save the profile to disk BasePath = GetBaseOVRPath(true); // create the base directory if it doesn't exist String path = GetProfilePath(); ProfileCache->Save(path); Changed = false; } // Returns a profile with all system default values Profile* ProfileManager::GetDefaultProfile(HmdTypeEnum 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. // Biometric data 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, OVR_DEFAULT_EYE_HEIGHT); profile->SetFloatValue(OVR_KEY_IPD, OVR_DEFAULT_IPD); float half_ipd[2] = { OVR_DEFAULT_IPD / 2, OVR_DEFAULT_IPD / 2 }; profile->SetFloatValues(OVR_KEY_EYE_TO_NOSE_DISTANCE, half_ipd, 2); 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); // Device specific data if (device != HmdType_None) { if (device == HmdType_CrystalCoveProto || device == HmdType_DK2) { profile->SetValue("EyeCup", "A"); profile->SetIntValue(OVR_KEY_EYE_RELIEF_DIAL, OVR_DEFAULT_EYE_RELIEF_DIAL); // TODO: These defaults are a little bogus and designed for continuity with 0.3 // eye-relief values. We need better measurement-based numbers in future releases float max_eye_plate[2] = { 0.01965f + 0.018f, 0.01965f + 0.018f }; profile->SetFloatValues(OVR_KEY_MAX_EYE_TO_PLATE_DISTANCE, max_eye_plate, 2); } else { // DK1 and DKHD variants profile->SetValue("EyeCup", "A"); profile->SetIntValue(OVR_KEY_EYE_RELIEF_DIAL, OVR_DEFAULT_EYE_RELIEF_DIAL); // TODO: These defaults are a little bogus and designed for continuity with 0.3 // DK1 distortion. We need better measurement-based numbers in future releases float max_eye_plate[2] = { 0.02357f + 0.017f, 0.02357f + 0.017f }; profile->SetFloatValues(OVR_KEY_MAX_EYE_TO_PLATE_DISTANCE, max_eye_plate, 2); } } return profile; } //------------------------------------------------------------------------------ void ProfileManager::Read() { LoadCache(false); } // Populates 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); ClearProfileData(); String path = GetProfilePath(); Ptr root = *JSON::Load(path); if (root == NULL) { path = BasePath + "/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 && 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); OVR_ASSERT(item1 && item2); if(!item1 || !item2) return; // Create the new profile database Ptr 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; pGetNextItem(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 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 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 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; } bool ProfileManager::HasUser(const char* user) { Lock::Locker lockScope(&ProfileLock); if (ProfileCache == NULL) { // Load the cache LoadCache(false); if (ProfileCache == NULL) return false; } JSON* users = ProfileCache->GetItemByName("Users"); if (users == NULL) return false; // 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) { return true; } user_item = users->GetNextItem(user_item); } return false; } // 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 user_items; FilterTaggedData(tagged_data, "User", user, user_items); for (unsigned int i=0; iRemoveNode(); user_items[i]->Release(); Changed = true; } return Changed; } Profile* ProfileManager::CreateProfile() { Profile* profile = new Profile(BasePath); return profile; } const char* ProfileManager::GetDefaultUser(const ProfileDeviceKey& deviceKey) { const char* product_str = deviceKey.ProductName.IsEmpty() ? NULL : deviceKey.ProductName.ToCStr(); const char* serial_str = deviceKey.PrintedSerial.IsEmpty() ? NULL : deviceKey.PrintedSerial.ToCStr(); return GetDefaultUser(product_str, serial_str); } // Returns the name of the profile that is marked as the current default user. const char* ProfileManager::GetDefaultUser(const char* product, const char* serial) { const char* tag_names[2] = {"Product", "Serial"}; const char* tags[2]; if (product && serial) { tags[0] = product; tags[1] = serial; // Look for a default user on this specific device Ptr 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 ProfileDeviceKey& deviceKey, const char* user) { const char* tag_names[2] = {"Product", "Serial"}; const char* tags[2]; const char* product_str = deviceKey.ProductName.IsEmpty() ? NULL : deviceKey.ProductName.ToCStr(); const char* serial_str = deviceKey.PrintedSerial.IsEmpty() ? NULL : deviceKey.PrintedSerial.ToCStr(); if (product_str && serial_str) { tags[0] = product_str; tags[1] = serial_str; Ptr 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(BasePath); 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; iAddStringItem(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; iValues.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 Changed = true; if (value->Type == JSON_String) vals->AddStringItem(value->Name, value->Value); else if (value->Type == JSON_Bool) vals->AddBoolItem(value->Name, ((int)value->dValue != 0)); else if (value->Type == JSON_Number) vals->AddNumberItem(value->Name, value->dValue); else if (value->Type == JSON_Array) vals->AddItem(value->Name, value->Copy()); else { OVR_ASSERT(false); Changed = false; } } } return true; } //----------------------------------------------------------------------------- Profile* ProfileManager::GetDefaultUserProfile(const ProfileDeviceKey& deviceKey) { const char* userName = GetDefaultUser(deviceKey); Profile* profile = GetProfile(deviceKey, userName); if (!profile) { profile = GetDefaultProfile(deviceKey.HmdType); } return profile; } //----------------------------------------------------------------------------- Profile* ProfileManager::GetProfile(const ProfileDeviceKey& deviceKey, const char* user) { Lock::Locker lockScope(&ProfileLock); if (ProfileCache == NULL) { // Load the cache LoadCache(false); if (ProfileCache == NULL) return NULL; } Profile* profile = new Profile(BasePath); if (deviceKey.Valid) { if (!profile->LoadDeviceProfile(deviceKey) && (user == NULL)) { profile->Release(); return NULL; } } if (user) { const char* product_str = deviceKey.ProductName.IsEmpty() ? NULL : deviceKey.ProductName.ToCStr(); const char* serial_str = deviceKey.PrintedSerial.IsEmpty() ? NULL : deviceKey.PrintedSerial.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; iRelease(); 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 productId, const char* printedSerialNumber) { if (printedSerialNumber[0] == 0) return false; String path = BasePath + "/Devices.json"; // Load the device profiles Ptr 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 == productId) && (serial_item->Value == printedSerialNumber)) { // 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; } #if 0 //----------------------------------------------------------------------------- static int BCDByte(unsigned int byte) { int digit1 = (byte >> 4) & 0x000f; int digit2 = byte & 0x000f; int decimal = digit1 * 10 + digit2; return decimal; } #endif //----------------------------------------------------------------------------- bool Profile::LoadDeviceProfile(const ProfileDeviceKey& deviceKey) { bool success = false; if (!deviceKey.Valid) return false; #if 0 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 { #endif // 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(deviceKey.ProductId, deviceKey.PrintedSerial); //} 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; } return 0; } //----------------------------------------------------------------------------- void Profile::SetValue(JSON* val) { if (val == NULL) return; 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) { // 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; iRemoveLast(); 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; iRemoveLast(); 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]); } //------------------------------------------------------------------------------ bool Profile::IsDefaultProfile() { return 0 == OVR::String::CompareNoCase("Default", GetValue(OVR_KEY_NAME)); } } // namespace OVR