This commit is contained in:
Liza 2024-03-21 19:56:30 +01:00
parent 24e5bafeb9
commit d6ba65e939
Signed by: liza
GPG Key ID: 7199F8D727D55F67
4 changed files with 145 additions and 126 deletions

View File

@ -1,17 +1,13 @@
using System;
using System.Collections.Generic;
using System.IO.Pipes;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using AutoRetainerAPI;
using Dalamud;
using Dalamud.Game;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Memory;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using ECommons;
@ -26,6 +22,7 @@ namespace AutoShutdown;
public sealed class Plogon : IDalamudPlugin
{
private readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromSeconds(1) };
private readonly nint _skipMovieAddress;
private readonly byte[] _skipMovieOriginalBytes;
private readonly AutoRetainerApi _autoRetainerApi;
@ -69,7 +66,7 @@ public sealed class Plogon : IDalamudPlugin
private unsafe void FrameworkUpdate(IFramework _)
{
if (DateTime.Now > ShutdownAt && _gameGui.TryGetAddonByName<AtkUnitBase>("_TitleMenu", out var addon))
if (DateTime.Now > ShutdownAt && _gameGui.TryGetAddonByName<AtkUnitBase>("_TitleMenu", out var _))
{
Environment.Exit(0);
}
@ -126,22 +123,21 @@ public sealed class Plogon : IDalamudPlugin
{
TimeSpan next = nextVesselTimes.Min();
_pluginLog.Information($"Next vessel time: {next}");
Task.Factory.StartNew(async () =>
if (next != TimeSpan.Zero)
{
try
Task.Factory.StartNew(async () =>
{
await using NamedPipeClientStream pipe =
new NamedPipeClientStream(".", "ffxiv_TheWatcher", PipeDirection.Out);
await pipe.ConnectAsync(1000);
byte[] content = Encoding.UTF8.GetBytes($"{next.TotalSeconds}");
await pipe.WriteAsync(content, 0, content.Length);
}
catch (Exception e)
{
_pluginLog.Warning(e, "Unable to send IPC");
}
}, TaskCreationOptions.LongRunning);
try
{
await _httpClient.PostAsync(new Uri($"http://localhost:12994/{next.TotalSeconds:F0}"),
null);
}
catch (Exception e)
{
_pluginLog.Warning(e, "Unable to send IPC");
}
}, TaskCreationOptions.LongRunning);
}
}
}

73
TheWatcher/Http.cs Normal file
View File

@ -0,0 +1,73 @@
using System.Net;
using System.Text;
namespace TheWatcher;
internal sealed class Http
{
private readonly HttpListener _listener;
private readonly byte[] _data = "OK"u8.ToArray();
public Http()
{
_listener = new HttpListener();
// netsh http add urlacl url=http://+:12994/ user=username
_listener.Prefixes.Add("http://+:12994/");
_listener.Start();
}
public async Task RunAsync(CancellationToken token)
{
token.Register(Close);
while (!token.IsCancellationRequested)
{
// wait for the next request
HttpListenerContext ctx = await _listener.GetContextAsync();
HttpListenerRequest request = ctx.Request;
HttpListenerResponse response = ctx.Response;
if (request is { HttpMethod: "POST", Url.Segments.Length: >= 2 } &&
double.TryParse(request.Url.Segments[1].TrimEnd('/'), out double duration))
{
TimeSpan timeSpan = TimeSpan.FromSeconds(duration);
if (!SleepCtrl.Enabled)
{
Console.WriteLine($"[{DateTime.Now}] Ignoring sleep, not enabled: {timeSpan}");
}
else if (timeSpan < TimeSpan.FromMinutes(10))
{
Console.WriteLine($"[{DateTime.Now}] Not enough sleep duration: {timeSpan}");
}
else
{
_ = Task.Factory.StartNew(async () =>
{
DateTime until = DateTime.Now.Add(timeSpan).Add(TimeSpan.FromMinutes(-5));
Console.WriteLine($"[{DateTime.Now}] Waiting a few seconds before sleeping for {timeSpan} until {until}");
await Task.Delay(TimeSpan.FromSeconds(35), token);
if (SleepCtrl.Enabled)
SleepCtrl.SleepUntil(until);
else
Console.WriteLine($"[{DateTime.Now}] Ignoring sleep (II), not enabled: {timeSpan}");
}, TaskCreationOptions.LongRunning);
}
}
response.ContentType = "text/plain";
response.ContentEncoding = Encoding.UTF8;
response.ContentLength64 = _data.LongLength;
await response.OutputStream.WriteAsync(_data, token);
response.Close();
}
}
private void Close()
{
_listener.Close();
}
}

View File

@ -1,28 +1,25 @@
using System.Diagnostics;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Text;
namespace TheWatcher;
internal sealed class Program
{
private static CancellationToken CancellationToken { get; set; }
private static bool EnableSleep { get; set; }
private static readonly Http Http = new();
public static async Task Main(string[] args)
public static async Task Main()
{
Console.WriteLine("o.O");
using CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken = cts.Token;
_ = Task.Factory.StartNew(async () => { await RunServer(); }, TaskCreationOptions.LongRunning);
_ = Task.Factory.StartNew(async () => { await Http.RunAsync(CancellationToken); }, TaskCreationOptions.LongRunning);
Console.CancelKeyPress += (_, e) =>
{
EnableSleep = !EnableSleep;
Console.WriteLine($"[{DateTime.Now}] Sleep => {EnableSleep}");
SleepCtrl.Enabled = !SleepCtrl.Enabled;
Console.WriteLine($"[{DateTime.Now}] Sleep => {SleepCtrl.Enabled}");
e.Cancel = true;
};
@ -102,102 +99,4 @@ internal sealed class Program
}
}
}
private static void SleepUntil(DateTime dt)
{
Console.WriteLine($"[{DateTime.Now}] Sleeping until {dt}...");
nint wakeTimer = SetWakeAt(dt);
if (wakeTimer != nint.Zero)
{
bool suspended = NativeMethods.SetSuspendState(false, false, false);
if (suspended)
{
if (DateTime.Now < dt)
{
Console.WriteLine($"[{DateTime.Now}] Resumed from suspend, but expected to sleep until {dt} => deactivating sleep");
EnableSleep = false;
}
else
{
Console.WriteLine($"[{DateTime.Now}] Resumed from suspend");
}
}
else
Console.WriteLine(
$"[{DateTime.Now}] Not Suspended / {Marshal.GetLastPInvokeError()} - {Marshal.GetLastPInvokeErrorMessage()}");
NativeMethods.CancelWaitableTimer(wakeTimer);
}
else
{
Console.WriteLine(
$"[{DateTime.Now}] Not able to set wait timer / {Marshal.GetLastPInvokeError()} - {Marshal.GetLastPInvokeErrorMessage()}");
}
}
private static IntPtr SetWakeAt(DateTime dt)
{
NativeMethods.TimerCompleteDelegate? timerComplete = null;
// read the manual for SetWaitableTimer to understand how this number is interpreted.
long interval = dt.ToFileTimeUtc();
IntPtr handle = NativeMethods.CreateWaitableTimer(IntPtr.Zero, true, "WaitableTimer");
NativeMethods.SetWaitableTimer(handle, ref interval, 0, timerComplete, IntPtr.Zero, true);
return handle;
}
private static async Task RunServer()
{
while (!CancellationToken.IsCancellationRequested)
{
await using NamedPipeServerStream pipeServer =
new NamedPipeServerStream("ffxiv_TheWatcher", PipeDirection.In, 1, PipeTransmissionMode.Message);
Console.WriteLine($"[{DateTime.Now}] Waiting for client...");
await pipeServer.WaitForConnectionAsync();
Console.WriteLine($"[{DateTime.Now}] Client connected...");
try
{
string message = await ReadMessage(pipeServer);
Console.WriteLine($"[{DateTime.Now}] Client message: {message}");
if (!EnableSleep)
{
Console.WriteLine($"[{DateTime.Now}] Ignoring sleep");
}
else if (double.TryParse(message, out double duration))
{
TimeSpan timeSpan = TimeSpan.FromSeconds(duration);
if (timeSpan >= TimeSpan.FromMinutes(10))
{
DateTime sleep = DateTime.Now.Add(timeSpan).Add(TimeSpan.FromMinutes(-5));
await Task.Delay(TimeSpan.FromSeconds(35));
SleepUntil(sleep);
}
else
Console.WriteLine($"[{DateTime.Now}] Not enough sleep duration");
}
}
catch (IOException e)
{
Console.WriteLine("ERROR: {0}", e.Message);
}
Console.WriteLine($"[{DateTime.Now}] Finished client loop...");
}
}
private static async Task<string> ReadMessage(PipeStream pipe)
{
byte[] buffer = new byte[1024];
using var ms = new MemoryStream();
do
{
var readBytes = await pipe.ReadAsync(buffer, 0, buffer.Length);
await ms.WriteAsync(buffer, 0, readBytes);
} while (!pipe.IsMessageComplete);
return Encoding.UTF8.GetString(ms.ToArray());
}
}

51
TheWatcher/SleepCtrl.cs Normal file
View File

@ -0,0 +1,51 @@
using System.Runtime.InteropServices;
namespace TheWatcher;
public class SleepCtrl
{
public static bool Enabled { get; set; }
public static void SleepUntil(DateTime dt)
{
Console.WriteLine($"[{DateTime.Now}] Sleeping until {dt}...");
nint wakeTimer = SetWakeAt(dt);
if (wakeTimer != nint.Zero)
{
bool suspended = NativeMethods.SetSuspendState(false, false, false);
if (suspended)
{
if (DateTime.Now < dt)
{
Console.WriteLine($"[{DateTime.Now}] Resumed from suspend, but expected to sleep until {dt} => deactivating sleep");
Enabled = false;
}
else
{
Console.WriteLine($"[{DateTime.Now}] Resumed from suspend");
}
}
else
Console.WriteLine(
$"[{DateTime.Now}] Not Suspended / {Marshal.GetLastPInvokeError()} - {Marshal.GetLastPInvokeErrorMessage()}");
NativeMethods.CancelWaitableTimer(wakeTimer);
}
else
{
Console.WriteLine(
$"[{DateTime.Now}] Not able to set wait timer / {Marshal.GetLastPInvokeError()} - {Marshal.GetLastPInvokeErrorMessage()}");
}
}
private static IntPtr SetWakeAt(DateTime dt)
{
NativeMethods.TimerCompleteDelegate? timerComplete = null;
// read the manual for SetWaitableTimer to understand how this number is interpreted.
long interval = dt.ToFileTimeUtc();
IntPtr handle = NativeMethods.CreateWaitableTimer(IntPtr.Zero, true, "WaitableTimer");
NativeMethods.SetWaitableTimer(handle, ref interval, 0, timerComplete, IntPtr.Zero, true);
return handle;
}
}