Skip to content

Commit

Permalink
UI: Rewrite profile system to enable user-provided storage location
Browse files Browse the repository at this point in the history
This change enables loading profiles from locations different than
OBS' own configuration directory.

It also rewrites profile management in the app to work off an in-memory
collection of profiles found on disk and does not require iterating
over directory contents for most profile interactions by the app.
  • Loading branch information
PatTheMav committed Aug 14, 2024
1 parent 8ad6a1a commit 528bce8
Show file tree
Hide file tree
Showing 9 changed files with 1,584 additions and 1,398 deletions.
580 changes: 272 additions & 308 deletions UI/api-interface.cpp

Large diffs are not rendered by default.

206 changes: 79 additions & 127 deletions UI/obs-app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -609,109 +609,41 @@ static bool MakeUserDirs()
return true;
}

static bool MakeUserProfileDirs()
{
char path[512];

if (GetConfigPath(path, sizeof(path), "obs-studio/basic/profiles") <= 0)
return false;
if (!do_mkdir(path))
return false;

if (GetConfigPath(path, sizeof(path), "obs-studio/basic/scenes") <= 0)
return false;
if (!do_mkdir(path))
return false;
constexpr std::string_view OBSProfileSubDirectory = "obs-studio/basic/profiles";
constexpr std::string_view OBSScenesSubDirectory = "obs-studio/basic/scenes";

return true;
}

static string GetProfileDirFromName(const char *name)
static bool MakeUserProfileDirs()
{
string outputPath;
os_glob_t *glob;
char path[512];

if (GetConfigPath(path, sizeof(path), "obs-studio/basic/profiles") <= 0)
return outputPath;

strcat(path, "/*");

if (os_glob(path, 0, &glob) != 0)
return outputPath;

for (size_t i = 0; i < glob->gl_pathc; i++) {
struct os_globent ent = glob->gl_pathv[i];
if (!ent.directory)
continue;

strcpy(path, ent.path);
strcat(path, "/basic.ini");

ConfigFile config;
if (config.Open(path, CONFIG_OPEN_EXISTING) != 0)
continue;

const char *curName =
config_get_string(config, "General", "Name");
if (astrcmpi(curName, name) == 0) {
outputPath = ent.path;
break;
const std::filesystem::path userProfilePath{
std::filesystem::path(App()->userProfilesLocation) /
OBSProfileSubDirectory.data()};
const std::filesystem::path userScenesPath{
std::filesystem::path(App()->userScenesLocation) /
OBSScenesSubDirectory.data()};

if (!filesystem::exists(userProfilePath)) {
try {
filesystem::create_directories(userProfilePath);
} catch (const filesystem::filesystem_error &error) {
blog(LOG_ERROR,
"Failed to create user profile directory '%s'\n%s",
userProfilePath.c_str(), error.what());
return false;
}
}

os_globfree(glob);

if (!outputPath.empty()) {
replace(outputPath.begin(), outputPath.end(), '\\', '/');
const char *start = strrchr(outputPath.c_str(), '/');
if (start)
outputPath.erase(0, start - outputPath.c_str() + 1);
}

return outputPath;
}

static string GetSceneCollectionFileFromName(const char *name)
{
string outputPath;
os_glob_t *glob;
char path[512];

if (GetConfigPath(path, sizeof(path), "obs-studio/basic/scenes") <= 0)
return outputPath;

strcat(path, "/*.json");

if (os_glob(path, 0, &glob) != 0)
return outputPath;

for (size_t i = 0; i < glob->gl_pathc; i++) {
struct os_globent ent = glob->gl_pathv[i];
if (ent.directory)
continue;

OBSDataAutoRelease data =
obs_data_create_from_json_file_safe(ent.path, "bak");
const char *curName = obs_data_get_string(data, "name");

if (astrcmpi(name, curName) == 0) {
outputPath = ent.path;
break;
if (!filesystem::exists(userScenesPath)) {
try {
filesystem::create_directories(userScenesPath);
} catch (const filesystem::filesystem_error &error) {
blog(LOG_ERROR,
"Failed to create user scene collection directory '%s'\n%s",
userScenesPath.c_str(), error.what());
return false;
}
}

os_globfree(glob);

if (!outputPath.empty()) {
outputPath.resize(outputPath.size() - 5);
replace(outputPath.begin(), outputPath.end(), '\\', '/');
const char *start = strrchr(outputPath.c_str(), '/');
if (start)
outputPath.erase(0, start - outputPath.c_str() + 1);
}

return outputPath;
return true;
}

bool OBSApp::UpdatePre22MultiviewLayout(const char *layout)
Expand Down Expand Up @@ -1198,56 +1130,76 @@ OBSApp::~OBSApp()
static void move_basic_to_profiles(void)
{
char path[512];
char new_path[512];
os_glob_t *glob;

/* if not first time use */
if (GetConfigPath(path, 512, "obs-studio/basic") <= 0)
return;
if (!os_file_exists(path))
if (GetAppConfigPath(path, 512, "obs-studio/basic") <= 0) {
return;
}

/* if the profiles directory doesn't already exist */
if (GetConfigPath(new_path, 512, "obs-studio/basic/profiles") <= 0)
return;
if (os_file_exists(new_path))
return;
const filesystem::path basicPath{path};

if (os_mkdir(new_path) == MKDIR_ERROR)
if (!filesystem::exists(basicPath)) {
return;
}

strcat(new_path, "/");
strcat(new_path, Str("Untitled"));
if (os_mkdir(new_path) == MKDIR_ERROR)
const string &userProfileLocation = App()->userProfilesLocation;

const filesystem::path profilesPath{userProfileLocation +
"/obs-studio/basic/profiles"};

if (filesystem::exists(profilesPath)) {
return;
}

strcat(path, "/*.*");
if (os_glob(path, 0, &glob) != 0)
try {
filesystem::create_directories(profilesPath);
} catch (const filesystem::filesystem_error &error) {
blog(LOG_ERROR,
"Failed to create profiles directory for migration from basic profile\n%s",
error.what());
return;
}

strcpy(path, new_path);
const filesystem::path newProfilePath{profilesPath / Str("Untitled")};

for (size_t i = 0; i < glob->gl_pathc; i++) {
struct os_globent ent = glob->gl_pathv[i];
char *file;
try {
filesystem::create_directory(newProfilePath);
} catch (const filesystem::filesystem_error &error) {
blog(LOG_ERROR,
"Failed to create profile directory for 'Untitled'\n%s",
error.what());
return;
}

if (ent.directory)
for (auto &entry : filesystem::directory_iterator(basicPath)) {
if (entry.is_directory()) {
continue;
}

file = strrchr(ent.path, '/');
if (!file++)
if (entry.path().filename() == "scenes.json") {
continue;
}

if (astrcmpi(file, "scenes.json") == 0)
continue;
const filesystem::path destinationFile =
newProfilePath / entry.path().filename();

strcpy(new_path, path);
strcat(new_path, "/");
strcat(new_path, file);
os_rename(ent.path, new_path);
}
try {
filesystem::copy(entry.path(), destinationFile);
} catch (const filesystem::filesystem_error &error) {
blog(LOG_ERROR,
"Failed to copy basic profile file '%s' to new profile 'Untitled'\n%s",
entry.path().filename().c_str(), error.what());

error_code errorCode;
filesystem::remove_all(newProfilePath, errorCode);

os_globfree(glob);
if (errorCode) {
blog(LOG_ERROR,
"Failed to clean up new profile directory for 'Untitled'");
}

return;
}
}
}

static void move_basic_to_scene_collections(void)
Expand Down
19 changes: 12 additions & 7 deletions UI/window-basic-auto-config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,23 @@ extern QCefCookieManager *panel_cookies;

/* ------------------------------------------------------------------------- */

#define SERVICE_PATH "service.json"
constexpr std::string_view OBSServiceFileName = "service.json";

static OBSData OpenServiceSettings(std::string &type)
{
char serviceJsonPath[512];
int ret = GetProfilePath(serviceJsonPath, sizeof(serviceJsonPath),
SERVICE_PATH);
if (ret <= 0)
const OBSBasic *basic =
reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
const OBSProfile &currentProfile = basic->GetCurrentProfile();

const std::filesystem::path jsonFilePath{currentProfile.path /
OBSServiceFileName.data()};

if (!std::filesystem::exists(jsonFilePath)) {
return OBSData();
}

OBSDataAutoRelease data =
obs_data_create_from_json_file_safe(serviceJsonPath, "bak");
OBSDataAutoRelease data = obs_data_create_from_json_file_safe(
jsonFilePath.u8string().c_str(), "bak");

obs_data_set_default_string(data, "type", "rtmp_common");
type = obs_data_get_string(data, "type");
Expand Down
19 changes: 14 additions & 5 deletions UI/window-basic-main-outputs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1612,19 +1612,28 @@ struct AdvancedOutput : BasicOutputHandler {

static OBSData GetDataFromJsonFile(const char *jsonFile)
{
char fullPath[512];
const OBSBasic *basic =
reinterpret_cast<OBSBasic *>(App()->GetMainWindow());

const OBSProfile &currentProfile = basic->GetCurrentProfile();

const std::filesystem::path jsonFilePath{currentProfile.path /
jsonFile};

OBSDataAutoRelease data = nullptr;

int ret = GetProfilePath(fullPath, sizeof(fullPath), jsonFile);
if (ret > 0) {
BPtr<char> jsonData = os_quick_read_utf8_file(fullPath);
if (!jsonFilePath.empty()) {
BPtr<char> jsonData = os_quick_read_utf8_file(
jsonFilePath.u8string().c_str());

if (!!jsonData) {
data = obs_data_create_from_json(jsonData);
}
}

if (!data)
if (!data) {
data = obs_data_create();
}

return data.Get();
}
Expand Down
Loading

0 comments on commit 528bce8

Please sign in to comment.