<commit_before>// Copyright (c) 2015, <NAME>.
// Distributed under the BSD 2-Clause License (see LICENSE.txt for details)

#include "Settings.h"

#pragma comment(lib, "Shlwapi.lib")

#include <algorithm>
#include <ctime>
#include <ShlObj.h>
#include <Shlwapi.h>

#include "DefaultSettings.h"
#include "Error.h"
#include "HotkeyInfo.h"
#include "LanguageTranslator.h"
#include "Logger.h"
#include "Monitor.h"
#include "Skin/Skin.h"
#include "StringUtils.h"

std::wstring Settings::_appDir(L"");
Settings *Settings::instance;

std::vector<std::wstring> Settings::OSDPosNames = {
    L"Top",
    L"Left",
    L"Right",
    L"Bottom",
    L"Center",
    L"Top-left",
    L"Top-right",
    L"Bottom-left",
    L"Bottom-right",
    L"Custom"
};

Settings *Settings::Instance() {
    if (instance == NULL) {
        instance = new Settings();
    }
    return instance;
}

void Settings::Load() {
    /* First, clean up (if needed) */
    delete _translator;
    _translator = NULL;

    _file = SettingsFile();
    CLOG(L"Loading settings: %s", _file.c_str());

    FILE *fp;
    _wfopen_s(&fp, _file.c_str(), L"rb");
    if (fp == NULL) {
        QCLOG(L"Failed to open file!");
        LoadEmptySettings();
        return;
    }

    tinyxml2::XMLError result = _xml.LoadFile(fp);
    fclose(fp);
    if (result != tinyxml2::XMLError::XML_SUCCESS) {
        LoadEmptySettings();
        return;
    }

    _root = _xml.GetDocument()->FirstChildElement("settings");
    if (_root == NULL) {
        Error::ErrorMessage(Error::GENERR_MISSING_XML, L"<settings>");
        LoadEmptySettings();
        return;
    }
}

void Settings::LoadEmptySettings() {
    _xml.Clear();
    _xml.InsertFirstChild(_xml.NewDeclaration());
    _root = _xml.NewElement("settings");
    _xml.GetDocument()->InsertEndChild(_root);
}

int Settings::Save() {
    CreateSettingsDir();
    FILE *stream;
    errno_t err = _wfopen_s(&stream, _file.c_str(), L"w");
    if (err != 0 || stream == NULL) {
        CLOG(L"Could not open settings file for writing!");
        return 100 + err;
    }
    tinyxml2::XMLError result = _xml.SaveFile(stream);
    fclose(stream);
    return result;
}

bool Settings::Portable() {
    std::wstring portableSettings
        = AppDir() + L"\\" + DefaultSettings::SettingsFileName;
    if (PathFileExists(portableSettings.c_str()) == TRUE) {
        return true;
    }

    return false;
}

std::wstring Settings::SettingsDir() {
    /* First, is this a portable installation? */
    if (Portable()) {
        return AppDir();
    }

    /* If the install isn't portable, use the roaming appdata directory. */
    wchar_t appData[MAX_PATH];
    HRESULT hr = SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, NULL, appData);
    if (FAILED(hr)) {
        HRESULT hr = SHGetFolderPath(
            NULL, CSIDL_LOCAL_APPDATA, NULL, NULL, appData);

        if (FAILED(hr)) {
            // TODO: This probably warrants an error message!
            return AppDir();
        }
    }

    return std::wstring(appData) + L"\\3RVX";
}

void Settings::CreateSettingsDir() {
    std::wstring settingsDir = SettingsDir();
    CLOG(L"Creating settings directory: %s", settingsDir.c_str());

    settingsDir = L"\\\\?\\" + settingsDir; /* Use long file path (\\?\) */
    BOOL result = CreateDirectory(settingsDir.c_str(), NULL);
    if (result == FALSE) {
        if (GetLastError() == ERROR_ALREADY_EXISTS) {
            QCLOG(L"Directory already exists.");
            return;
        }

        if (GetLastError() == ERROR_PATH_NOT_FOUND) {
            QCLOG(L"Path not found!");
            // TODO: error message?
        }
    }
}

std::wstring Settings::SettingsFile() {
    return SettingsDir() + std::wstring(L"\\")
        + DefaultSettings::SettingsFileName;
}

std::wstring Settings::AppDir() {
    if (_appDir.empty()) {
        wchar_t path[MAX_PATH] = { 0 };
        if (GetModuleFileName(NULL, path, MAX_PATH)) {
            PathRemoveFileSpec(path);
        }
        _appDir = std::wstring(path);
    }
    return _appDir;
}

std::wstring Settings::SkinDir() {
    return AppDir() + L"\\" + DefaultSettings::SkinDirName;
}

std::wstring Settings::MainApp() {
    return Settings::AppDir() + L"\\" + DefaultSettings::MainAppName;
}

std::wstring Settings::SettingsApp() {
    return Settings::AppDir() + L"\\" + DefaultSettings::SettingsAppName;
}

std::wstring Settings::LanguagesDir() {
    return AppDir() + L"\\" + DefaultSettings::LanguageDirName;
}

void Settings::LaunchSettingsApp() {
    std::wstring app = SettingsApp();

    CLOG(L"Opening Settings App: %s", app.c_str());
    int exec = (int) ShellExecute(
        NULL, L"open", app.c_str(), NULL, NULL, SW_SHOWNORMAL);

    if (exec <= 32) {
        Error::ErrorMessage(Error::GENERR_NOTFOUND, app);
    }
}

std::wstring Settings::AudioDeviceID() {
    return GetText(XML_AUDIODEV);
}

int Settings::VolumeCurveAdjustment() {
    return GetInt(XML_CURVE_ADJUST, 0);
}

void Settings::VolumeCurveAdjustment(int value) {
    SetElementValue(XML_CURVE_ADJUST, value);<commit_msg>Don't allow taper levels below 0<commit_after>// Copyright (c) 2015, <NAME>.
// Distributed under the BSD 2-Clause License (see LICENSE.txt for details)

#include "Settings.h"

#pragma comment(lib, "Shlwapi.lib")

#include <algorithm>
#include <ctime>
#include <ShlObj.h>
#include <Shlwapi.h>

#include "DefaultSettings.h"
#include "Error.h"
#include "HotkeyInfo.h"
#include "LanguageTranslator.h"
#include "Logger.h"
#include "Monitor.h"
#include "Skin/Skin.h"
#include "StringUtils.h"

std::wstring Settings::_appDir(L"");
Settings *Settings::instance;

std::vector<std::wstring> Settings::OSDPosNames = {
    L"Top",
    L"Left",
    L"Right",
    L"Bottom",
    L"Center",
    L"Top-left",
    L"Top-right",
    L"Bottom-left",
    L"Bottom-right",
    L"Custom"
};

Settings *Settings::Instance() {
    if (instance == NULL) {
        instance = new Settings();
    }
    return instance;
}

void Settings::Load() {
    /* First, clean up (if needed) */
    delete _translator;
    _translator = NULL;

    _file = SettingsFile();
    CLOG(L"Loading settings: %s", _file.c_str());

    FILE *fp;
    _wfopen_s(&fp, _file.c_str(), L"rb");
    if (fp == NULL) {
        QCLOG(L"Failed to open file!");
        LoadEmptySettings();
        return;
    }

    tinyxml2::XMLError result = _xml.LoadFile(fp);
    fclose(fp);
    if (result != tinyxml2::XMLError::XML_SUCCESS) {
        LoadEmptySettings();
        return;
    }

    _root = _xml.GetDocument()->FirstChildElement("settings");
    if (_root == NULL) {
        Error::ErrorMessage(Error::GENERR_MISSING_XML, L"<settings>");
        LoadEmptySettings();
        return;
    }
}

void Settings::LoadEmptySettings() {
    _xml.Clear();
    _xml.InsertFirstChild(_xml.NewDeclaration());
    _root = _xml.NewElement("settings");
    _xml.GetDocument()->InsertEndChild(_root);
}

int Settings::Save() {
    CreateSettingsDir();
    FILE *stream;
    errno_t err = _wfopen_s(&stream, _file.c_str(), L"w");
    if (err != 0 || stream == NULL) {
        CLOG(L"Could not open settings file for writing!");
        return 100 + err;
    }
    tinyxml2::XMLError result = _xml.SaveFile(stream);
    fclose(stream);
    return result;
}

bool Settings::Portable() {
    std::wstring portableSettings
        = AppDir() + L"\\" + DefaultSettings::SettingsFileName;
    if (PathFileExists(portableSettings.c_str()) == TRUE) {
        return true;
    }

    return false;
}

std::wstring Settings::SettingsDir() {
    /* First, is this a portable installation? */
    if (Portable()) {
        return AppDir();
    }

    /* If the install isn't portable, use the roaming appdata directory. */
    wchar_t appData[MAX_PATH];
    HRESULT hr = SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, NULL, appData);
    if (FAILED(hr)) {
        HRESULT hr = SHGetFolderPath(
            NULL, CSIDL_LOCAL_APPDATA, NULL, NULL, appData);

        if (FAILED(hr)) {
            // TODO: This probably warrants an error message!
            return AppDir();
        }
    }

    return std::wstring(appData) + L"\\3RVX";
}

void Settings::CreateSettingsDir() {
    std::wstring settingsDir = SettingsDir();
    CLOG(L"Creating settings directory: %s", settingsDir.c_str());

    settingsDir = L"\\\\?\\" + settingsDir; /* Use long file path (\\?\) */
    BOOL result = CreateDirectory(settingsDir.c_str(), NULL);
    if (result == FALSE) {
        if (GetLastError() == ERROR_ALREADY_EXISTS) {
            QCLOG(L"Directory already exists.");
            return;
        }

        if (GetLastError() == ERROR_PATH_NOT_FOUND) {
            QCLOG(L"Path not found!");
            // TODO: error message?
        }
    }
}

std::wstring Settings::SettingsFile() {
    return SettingsDir() + std::wstring(L"\\")
        + DefaultSettings::SettingsFileName;
}

std::wstring Settings::AppDir() {
    if (_appDir.empty()) {
        wchar_t path[MAX_PATH] = { 0 };
        if (GetModuleFileName(NULL, path, MAX_PATH)) {
            PathRemoveFileSpec(path);
        }
        _appDir = std::wstring(path);
    }
    return _appDir;
}

std::wstring Settings::SkinDir() {
    return AppDir() + L"\\" + DefaultSettings::SkinDirName;
}

std::wstring Settings::MainApp() {
    return Settings::AppDir() + L"\\" + DefaultSettings::MainAppName;
}

std::wstring Settings::SettingsApp() {
    return Settings::AppDir() + L"\\" + DefaultSettings::SettingsAppName;
}

std::wstring Settings::LanguagesDir() {
    return AppDir() + L"\\" + DefaultSettings::LanguageDirName;
}

void Settings::LaunchSettingsApp() {
    std::wstring app = SettingsApp();

    CLOG(L"Opening Settings App: %s", app.c_str());
    int exec = (int) ShellExecute(
        NULL, L"open", app.c_str(), NULL, NULL, SW_SHOWNORMAL);

    if (exec <= 32) {
        Error::ErrorMessage(Error::GENERR_NOTFOUND, app);
    }
}

std::wstring Settings::AudioDeviceID() {
    return GetText(XML_AUDIODEV);
}

int Settings::VolumeCurveAdjustment() {
    return GetInt(XML_CURVE_ADJUST, 0);
}

void Settings::VolumeCurveAdjustment(int value) {
    if (value < 0) {
        value = 0;
    }
    SetElementValue(XML_CURVE_ADJUST, value);
{'fraction_non_alphanumeric': 0.15446754014382819, 'fraction_numerical': 0.0036449610875775784, 'mean_word_length': 3.1285075233834894, 'pattern_counts': {'":': 0, '<': 20, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 22}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1629259', 'n_tokens_mistral': 3416, 'n_tokens_neox': 3340, 'n_words': 711}