fix: read/write the library via the host ingest inbox (de-privileged runner)
The host plugin runner is de-privileged now (LocalService) and can't read the interactive user's %APPDATA%\Playnite\ExtensionsData — so it never saw the export. Bridge it through the host's user-writable ingest inbox: - exporter (C#): also drop punktfunk-library.json into %ProgramData%\punktfunk\ingest\playnite (best-effort, only when a punktfunk host is present), alongside its ExtensionsData home. - plugin (TS): locateExport() reads <config_dir>/ingest/playnite first, then falls back to the ExtensionsData scan (same-user/SYSTEM runner, Linux). The watcher also watches the inbox so a drop reconciles within seconds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,10 +12,13 @@ using Playnite.SDK.Plugins;
|
||||
namespace PunktfunkSync
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes the Playnite library to <c>punktfunk-library.json</c> in this plugin's data directory
|
||||
/// (<c>…/Playnite/ExtensionsData/<this-id>/</c>) whenever it changes. The Punktfunk
|
||||
/// <c>playnite</c> plugin globs for that file and reconciles it into the host game library — this
|
||||
/// side stays a dumb, dependency-free exporter.
|
||||
/// Writes the Playnite library to <c>punktfunk-library.json</c> whenever it changes, to two
|
||||
/// places: this plugin's data directory (<c>…/Playnite/ExtensionsData/<this-id>/</c>) and —
|
||||
/// best-effort — the host's ingest inbox (<c>%ProgramData%\punktfunk\ingest\playnite\</c>). The
|
||||
/// second matters because the host's plugin runner is de-privileged (<c>NT AUTHORITY\LocalService</c>)
|
||||
/// and cannot read this user's <c>%APPDATA%</c>; <c>punktfunk-host plugins enable</c> makes the
|
||||
/// ingest inbox user-writable so we can drop the file where the runner can read it. This side
|
||||
/// stays a dumb, dependency-free exporter.
|
||||
/// </summary>
|
||||
public class PunktfunkSyncPlugin : GenericPlugin
|
||||
{
|
||||
@@ -27,12 +30,19 @@ namespace PunktfunkSync
|
||||
public override Guid Id { get; } = Guid.Parse("d9f1b3a2-7c64-4e58-9a1b-6f2e3c4d5a6b");
|
||||
|
||||
private readonly string exportPath;
|
||||
private readonly string ingestPath;
|
||||
private readonly Timer debounce;
|
||||
|
||||
public PunktfunkSyncPlugin(IPlayniteAPI api) : base(api)
|
||||
{
|
||||
Properties = new GenericPluginProperties { HasSettings = false };
|
||||
exportPath = Path.Combine(GetPluginUserDataPath(), "punktfunk-library.json");
|
||||
// The host runner is de-privileged and can't read our %APPDATA%; drop a copy in the
|
||||
// host's user-writable ingest inbox too (see WriteAtomic). Keep this in lockstep with the
|
||||
// TS reader's `ingestDir()` (`<config_dir>/ingest/playnite`).
|
||||
ingestPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
||||
"punktfunk", "ingest", "playnite", "punktfunk-library.json");
|
||||
|
||||
// Coalesce bursts of per-item events (a metadata download touches many games) into one write.
|
||||
debounce = new Timer(3000) { AutoReset = false };
|
||||
@@ -123,14 +133,37 @@ namespace PunktfunkSync
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Write via a temp file + move so a reader never sees a half-written export.</summary>
|
||||
/// <summary>
|
||||
/// Write the export to its ExtensionsData home (always) and to the host ingest inbox
|
||||
/// (best-effort — only when a punktfunk host is present on this box, so we never create a
|
||||
/// stray %ProgramData%\punktfunk on a machine that has no host).
|
||||
/// </summary>
|
||||
private void WriteAtomic(string json)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(exportPath));
|
||||
var tmp = exportPath + ".tmp";
|
||||
WriteAtomicTo(exportPath, json);
|
||||
try
|
||||
{
|
||||
var pfDir = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
||||
"punktfunk");
|
||||
if (Directory.Exists(pfDir)) WriteAtomicTo(ingestPath, json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// The inbox only exists (and is writable) where `plugins enable` has run; a failure
|
||||
// here just means the de-privileged runner falls back to the ExtensionsData scan.
|
||||
logger.Info($"Punktfunk Sync: ingest drop skipped ({ex.Message})");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Write via a temp file + move so a reader never sees a half-written export.</summary>
|
||||
private static void WriteAtomicTo(string path, string json)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
var tmp = path + ".tmp";
|
||||
File.WriteAllText(tmp, json);
|
||||
if (File.Exists(exportPath)) File.Delete(exportPath);
|
||||
File.Move(tmp, exportPath);
|
||||
if (File.Exists(path)) File.Delete(path);
|
||||
File.Move(tmp, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user