mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-23 19:49:43 +01:00
Use localappdata to store settings, instead of in WindowsApps (#304)
I'm smarter than that, really. As described in #302. You can't write into `WindowsApps`, where actual packages are installed. Instead, you need to use the local app data path. This replicates logic that we've got in the Terminal, for getting the right LocalAppData path, without using Windows.Storage. Original code looks like: ```c++ _TIL_INLINEPREFIX bool IsPackaged() { static const auto isPackaged = []() { UINT32 bufferLength = 0; const auto hr = GetCurrentPackageId(&bufferLength, nullptr); return hr != APPMODEL_ERROR_NO_PACKAGE; }(); return isPackaged; } std::filesystem::path GetBaseSettingsPath() { static auto baseSettingsPath = []() { /* some portable mode code we don't need */ wil::unique_cotaskmem_string localAppDataFolder; // KF_FLAG_FORCE_APP_DATA_REDIRECTION, when engaged, causes SHGet... to return // the new AppModel paths (Packages/xxx/RoamingState, etc.) for standard path requests. // Using this flag allows us to avoid Windows.Storage.ApplicationData completely. THROW_IF_FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_FORCE_APP_DATA_REDIRECTION, nullptr, &localAppDataFolder)); std::filesystem::path parentDirectoryForSettingsFile{ localAppDataFolder.get() }; if (!IsPackaged()) { parentDirectoryForSettingsFile /= UnpackagedSettingsFolderName; } // Create the directory if it doesn't exist std::filesystem::create_directories(parentDirectoryForSettingsFile); return parentDirectoryForSettingsFile; }(); return baseSettingsPath; } ``` I stuck this in a `Helpers.Utilities` class, because we will not be the only ones hitting this. Closes #302
This commit is contained in:
@@ -8,7 +8,6 @@ namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
[JsonSerializable(typeof(BookmarkData))]
|
||||
[JsonSerializable(typeof(string))]
|
||||
[JsonSerializable(typeof(string))]
|
||||
internal sealed partial class BookmarkDataContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
|
||||
@@ -111,13 +111,10 @@ public partial class BookmarksCommandProvider : CommandProvider
|
||||
|
||||
internal static string StateJsonPath()
|
||||
{
|
||||
// Get the path to our exe
|
||||
var path = System.Reflection.Assembly.GetExecutingAssembly().Location;
|
||||
|
||||
// Get the directory of the exe
|
||||
var directory = System.IO.Path.GetDirectoryName(path) ?? string.Empty;
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
// now, the state is just next to the exe
|
||||
return System.IO.Path.Combine(directory, "state.json");
|
||||
return System.IO.Path.Combine(directory, "bookmarks.json");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,11 +53,8 @@ public class SettingsManager : JsonSettingsManager
|
||||
|
||||
internal static string SettingsJsonPath()
|
||||
{
|
||||
// Get the path to our exe
|
||||
var path = System.Reflection.Assembly.GetExecutingAssembly().Location;
|
||||
|
||||
// Get the directory of the exe
|
||||
var directory = Path.GetDirectoryName(path) ?? string.Empty;
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
// now, the state is just next to the exe
|
||||
return Path.Combine(directory, "settings.json");
|
||||
|
||||
@@ -23,7 +23,7 @@ internal sealed partial class SettingsPage : FormPage
|
||||
{
|
||||
Name = Properties.Resources.settings_page_name;
|
||||
Icon = new("\uE713"); // Settings icon
|
||||
_settings = settingsManager.GetSettings();
|
||||
_settings = settingsManager.Settings;
|
||||
_settingsManager = settingsManager;
|
||||
|
||||
_settings.SettingsChanged += SettingsChanged;
|
||||
|
||||
@@ -8,20 +8,21 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Commands;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Properties;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
|
||||
public class SettingsManager
|
||||
public class SettingsManager : JsonSettingsManager
|
||||
{
|
||||
private readonly string _filePath;
|
||||
private readonly string _historyPath;
|
||||
private readonly Microsoft.CmdPal.Extensions.Helpers.Settings _settings = new();
|
||||
|
||||
private readonly List<ChoiceSetSetting.Choice> _choices = new()
|
||||
private static readonly string _namespace = "websearch";
|
||||
|
||||
private static string Namespaced(string propertyName) => $"{_namespace}.{propertyName}";
|
||||
|
||||
private static readonly List<ChoiceSetSetting.Choice> _choices = new()
|
||||
{
|
||||
new ChoiceSetSetting.Choice(Resources.history_none, Resources.history_none),
|
||||
new ChoiceSetSetting.Choice(Resources.history_1, Resources.history_1),
|
||||
@@ -30,8 +31,17 @@ public class SettingsManager
|
||||
new ChoiceSetSetting.Choice(Resources.history_20, Resources.history_20),
|
||||
};
|
||||
|
||||
private readonly ToggleSetting _globalIfURI = new(nameof(GlobalIfURI), Resources.plugin_global_if_uri, Resources.plugin_global_if_uri, false);
|
||||
private readonly ChoiceSetSetting _showHistory;
|
||||
private readonly ToggleSetting _globalIfURI = new(
|
||||
Namespaced(nameof(GlobalIfURI)),
|
||||
Resources.plugin_global_if_uri,
|
||||
Resources.plugin_global_if_uri,
|
||||
false);
|
||||
|
||||
private readonly ChoiceSetSetting _showHistory = new(
|
||||
Namespaced(nameof(ShowHistory)),
|
||||
Resources.plugin_show_history,
|
||||
Resources.plugin_show_history,
|
||||
_choices);
|
||||
|
||||
public bool GlobalIfURI => _globalIfURI.Value;
|
||||
|
||||
@@ -39,23 +49,17 @@ public class SettingsManager
|
||||
|
||||
internal static string SettingsJsonPath()
|
||||
{
|
||||
// Get the path to our exe
|
||||
var path = System.Reflection.Assembly.GetExecutingAssembly().Location;
|
||||
|
||||
// Get the directory of the exe
|
||||
var directory = Path.GetDirectoryName(path) ?? string.Empty;
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
// now, the state is just next to the exe
|
||||
return Path.Combine(directory, "websearch_state.json");
|
||||
return Path.Combine(directory, "settings.json");
|
||||
}
|
||||
|
||||
internal static string HistoryStateJsonPath()
|
||||
{
|
||||
// Get the path to our exe
|
||||
var path = System.Reflection.Assembly.GetExecutingAssembly().Location;
|
||||
|
||||
// Get the directory of the exe
|
||||
var directory = Path.GetDirectoryName(path) ?? string.Empty;
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
// now, the state is just next to the exe
|
||||
return Path.Combine(directory, "websearch_history.json");
|
||||
@@ -142,12 +146,11 @@ public class SettingsManager
|
||||
|
||||
public SettingsManager()
|
||||
{
|
||||
_filePath = SettingsJsonPath();
|
||||
FilePath = SettingsJsonPath();
|
||||
_historyPath = HistoryStateJsonPath();
|
||||
_showHistory = new(nameof(ShowHistory), Resources.plugin_show_history, Resources.plugin_show_history, _choices);
|
||||
|
||||
_settings.Add(_globalIfURI);
|
||||
_settings.Add(_showHistory);
|
||||
Settings.Add(_globalIfURI);
|
||||
Settings.Add(_showHistory);
|
||||
|
||||
// Load settings from file upon initialization
|
||||
LoadSettings();
|
||||
@@ -178,17 +181,11 @@ public class SettingsManager
|
||||
}
|
||||
}
|
||||
|
||||
public Microsoft.CmdPal.Extensions.Helpers.Settings GetSettings() => _settings;
|
||||
|
||||
public void SaveSettings()
|
||||
public override void SaveSettings()
|
||||
{
|
||||
base.SaveSettings();
|
||||
try
|
||||
{
|
||||
// Serialize the main dictionary to JSON and save it to the file
|
||||
var settingsJson = _settings.ToJson();
|
||||
|
||||
File.WriteAllText(_filePath, settingsJson);
|
||||
|
||||
if (ShowHistory == Resources.history_none)
|
||||
{
|
||||
ClearHistory();
|
||||
@@ -219,33 +216,4 @@ public class SettingsManager
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = ex.ToString() });
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadSettings()
|
||||
{
|
||||
if (!File.Exists(_filePath))
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "The provided settings file does not exist" });
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Read the JSON content from the file
|
||||
var jsonContent = File.ReadAllText(_filePath);
|
||||
|
||||
// Is it valid JSON?
|
||||
if (JsonNode.Parse(jsonContent) is JsonObject savedSettings)
|
||||
{
|
||||
_settings.Update(jsonContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = "Failed to parse settings file as JsonObject." });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExtensionHost.LogMessage(new LogMessage() { Message = ex.ToString() });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ internal sealed partial class SettingsPage : FormPage
|
||||
{
|
||||
Name = Resources.settings_page_name;
|
||||
Icon = new("\uE713"); // Settings icon
|
||||
_settings = settingsManager.GetSettings();
|
||||
_settings = settingsManager.Settings;
|
||||
_settingsManager = settingsManager;
|
||||
|
||||
_settings.SettingsChanged += SettingsChanged;
|
||||
|
||||
@@ -90,11 +90,8 @@ public class SettingsManager : JsonSettingsManager
|
||||
|
||||
internal static string SettingsJsonPath()
|
||||
{
|
||||
// Get the path to our exe
|
||||
var path = System.Reflection.Assembly.GetExecutingAssembly().Location;
|
||||
|
||||
// Get the directory of the exe
|
||||
var directory = Path.GetDirectoryName(path) ?? string.Empty;
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
// now, the state is just next to the exe
|
||||
return Path.Combine(directory, "settings.json");
|
||||
|
||||
@@ -25,7 +25,7 @@ internal sealed partial class SettingsPage : FormPage
|
||||
Name = Resources.windowwalker_settings_name;
|
||||
Icon = new("\uE713"); // Settings icon
|
||||
_settingsManager = SettingsManager.Instance;
|
||||
_settings = _settingsManager.GetSettings();
|
||||
_settings = _settingsManager.Settings;
|
||||
|
||||
_settings.SettingsChanged += SettingsChanged;
|
||||
}
|
||||
|
||||
@@ -42,11 +42,8 @@ public class SettingsManager : JsonSettingsManager
|
||||
|
||||
internal static string SettingsJsonPath()
|
||||
{
|
||||
// Get the path to our exe
|
||||
var path = System.Reflection.Assembly.GetExecutingAssembly().Location;
|
||||
|
||||
// Get the directory of the exe
|
||||
var directory = Path.GetDirectoryName(path) ?? string.Empty;
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
// now, the state is just next to the exe
|
||||
return Path.Combine(directory, "settings.json");
|
||||
|
||||
@@ -24,7 +24,7 @@ internal sealed partial class SettingsPage : FormPage
|
||||
{
|
||||
Name = Resources.settings_page_name;
|
||||
Icon = new("\uE713"); // Settings icon
|
||||
_settings = settingsManager.GetSettings();
|
||||
_settings = settingsManager.Settings;
|
||||
_settingsManager = settingsManager;
|
||||
|
||||
_settings.SettingsChanged += SettingsChanged;
|
||||
|
||||
@@ -90,11 +90,8 @@ public partial class SpongebotPage : MarkdownPage, IFallbackHandler
|
||||
|
||||
internal static string StateJsonPath()
|
||||
{
|
||||
// Get the path to our exe
|
||||
var path = System.Reflection.Assembly.GetExecutingAssembly().Location;
|
||||
|
||||
// Get the directory of the exe
|
||||
var directory = Path.GetDirectoryName(path) ?? string.Empty;
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
// now, the state is just next to the exe
|
||||
return Path.Combine(directory, "state.json");
|
||||
|
||||
@@ -2,12 +2,8 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace YouTubeExtension.Actions;
|
||||
|
||||
@@ -15,11 +11,8 @@ internal sealed class YouTubeHelper
|
||||
{
|
||||
internal static string StateJsonPath()
|
||||
{
|
||||
// Get the path to our exe
|
||||
var path = System.Reflection.Assembly.GetExecutingAssembly().Location;
|
||||
|
||||
// Get the directory of the exe
|
||||
var directory = Path.GetDirectoryName(path) ?? string.Empty;
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
// now, the state is just next to the exe
|
||||
return Path.Combine(directory, "state.json");
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Settings;
|
||||
using Windows.Foundation;
|
||||
|
||||
@@ -119,11 +120,8 @@ public partial class SettingsModel : ObservableObject
|
||||
|
||||
internal static string SettingsJsonPath()
|
||||
{
|
||||
// Get the path to our exe
|
||||
var path = System.Reflection.Assembly.GetExecutingAssembly().Location;
|
||||
|
||||
// Get the directory of the exe
|
||||
var directory = Path.GetDirectoryName(path) ?? string.Empty;
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
// now, the settings is just next to the exe
|
||||
return Path.Combine(directory, "settings.json");
|
||||
|
||||
@@ -22,12 +22,7 @@ public abstract class JsonSettingsManager
|
||||
Converters = { new JsonStringEnumConverter() },
|
||||
};
|
||||
|
||||
public Settings GetSettings()
|
||||
{
|
||||
return _settings;
|
||||
}
|
||||
|
||||
public void LoadSettings()
|
||||
public virtual void LoadSettings()
|
||||
{
|
||||
if (string.IsNullOrEmpty(FilePath))
|
||||
{
|
||||
@@ -62,7 +57,7 @@ public abstract class JsonSettingsManager
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
public virtual void SaveSettings()
|
||||
{
|
||||
if (string.IsNullOrEmpty(FilePath))
|
||||
{
|
||||
|
||||
@@ -2,4 +2,7 @@
|
||||
GetCurrentThread
|
||||
OpenThreadToken
|
||||
GetPackageFamilyNameFromToken
|
||||
CoRevertToSelf
|
||||
CoRevertToSelf
|
||||
SHGetKnownFolderPath
|
||||
KNOWN_FOLDER_FLAG
|
||||
GetCurrentPackageId
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.UI.Shell;
|
||||
|
||||
namespace Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312:Variable names should begin with lower-case letter", Justification = "This file has more than a couple Windows constants in it, which don't make sense to rename")]
|
||||
public class Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to produce a path to a settings folder which your app can use.
|
||||
/// If your app is running packaged, this will return the redirected local
|
||||
/// app data path (Packages/{your_pfn}/LocalState). If not, it'll return
|
||||
/// %LOCALAPPDATA%\{settingsFolderName}.
|
||||
///
|
||||
/// Does not ensure that the directory exists. Callers should call
|
||||
/// CreateDirectory before writing settings files to this directory.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// var directory = Utilities.BaseSettingsPath("Some.Unique.String.Here");
|
||||
/// Directory.CreateDirectory(directory);
|
||||
/// </example>
|
||||
/// <param name="settingsFolderName">A fallback directory name to use
|
||||
/// inside of %LocalAppData%, in the case this app is not currently running
|
||||
/// in a package context</param>
|
||||
/// <returns>The path to a folder to use for storing settings.</returns>
|
||||
public static string BaseSettingsPath(string settingsFolderName)
|
||||
{
|
||||
// KF_FLAG_FORCE_APP_DATA_REDIRECTION, when engaged, causes SHGet... to return
|
||||
// the new AppModel paths (Packages/xxx/RoamingState, etc.) for standard path requests.
|
||||
// Using this flag allows us to avoid Windows.Storage.ApplicationData completely.
|
||||
var FOLDERID_LocalAppData = new Guid("F1B32785-6FBA-4FCF-9D55-7B8E7F157091");
|
||||
var hr = PInvoke.SHGetKnownFolderPath(
|
||||
FOLDERID_LocalAppData,
|
||||
(uint)KNOWN_FOLDER_FLAG.KF_FLAG_FORCE_APP_DATA_REDIRECTION,
|
||||
null,
|
||||
out var localAppDataFolder);
|
||||
|
||||
if (hr.Succeeded)
|
||||
{
|
||||
var basePath = new string(localAppDataFolder.ToString());
|
||||
if (!IsPackaged())
|
||||
{
|
||||
basePath = Path.Combine(basePath, settingsFolderName);
|
||||
}
|
||||
|
||||
return basePath;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Marshal.GetExceptionForHR(hr.Value)!;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can be used to quickly determine if this process is running with package identity.
|
||||
/// </summary>
|
||||
/// <returns>true iff the process is running with package identity</returns>
|
||||
public static bool IsPackaged()
|
||||
{
|
||||
uint bufferSize = 0;
|
||||
var bytes = Array.Empty<byte>();
|
||||
|
||||
// CsWinRT apparently won't generate this constant
|
||||
var APPMODEL_ERROR_NO_PACKAGE = (WIN32_ERROR)15700;
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* p = bytes)
|
||||
{
|
||||
// We don't actually need the package ID. We just need to know
|
||||
// if we have a package or not, and APPMODEL_ERROR_NO_PACKAGE
|
||||
// is a quick way to find out.
|
||||
var win32Error = PInvoke.GetCurrentPackageId(ref bufferSize, p);
|
||||
return win32Error != APPMODEL_ERROR_NO_PACKAGE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user