Reimplement based on systemd's inhibit system

This commit is contained in:
Chris Josten 2021-01-22 19:06:44 +01:00
parent ceb06abce6
commit 6db58d07cb
10 changed files with 385 additions and 178 deletions

187
ober/source/app.d Normal file
View file

@ -0,0 +1,187 @@
import std.conv;
import std.datetime;
import std.experimental.logger;
import std.stdio;
import std.string;
import vibe.core.core;
import vibe.core.net;
import vibe.core.process;
import ddbus;
import ddbus.c_lib;
import msgpackrpc;
string appName = "Automatisch In- en Uitschakelen Ober";
extern (C) void close(int fileNo);
/**
* Represententeerd de toepassing
*/
struct Toep {
public:
immutable ushort PORT = 18_002;
immutable int m_waitingTime;
immutable string m_clientAddress;
immutable int CHECK_COUNT = 2;
Connection m_dbusCon;
bool m_keepRunning = true;
version(WithRPC) Client m_client;
this(int waitingTime, string clientAddress) {
this.m_waitingTime = waitingTime;
this.m_clientAddress = clientAddress;
this.m_dbusCon = connectToBus(DBusBusType.DBUS_BUS_SYSTEM);
}
int exec() {
info ("Can shut down: ", canShutdown());
version(WithRPC) {
TCPConnection conn = connectTCP(m_clientAddress, PORT);
Duration sleepTime = 500.msecs;
while(!conn) {
infof("Not connected, trying again in %s", sleepTime);
sleep(sleepTime);
conn = connectTCP(m_clientAddress, PORT);
}
info("Connected!");
m_client = new Client(conn);
writeln("Receive response: '", m_client.call!(string)("GetBootReason"), "'");
}
runTask({
import vibe.core.core : yield;
while(m_dbusCon.tick() && m_keepRunning) {
yield();
}
});
runTask({
FileDescriptor inhibitLock = FileDescriptor.none;
version(WithRPC) {
FileDescriptor sleepLock = FileDescriptor.none;
FileDescriptor shutdownLock = FileDescriptor.none;
}
// Get interfaces
BusName loginBus = busName("org.freedesktop.login1");
ObjectPath loginPath = ObjectPath("/org/freedesktop/login1");
InterfaceName loginIFace = interfaceName("org.freedesktop.login1.Manager");
PathIface loginManager = new PathIface(m_dbusCon, loginBus, loginPath, loginIFace);
PathIface loginManagerProps = new PathIface(m_dbusCon, loginBus, loginPath, interfaceName("org.freedesktop.DBus.Properties"));
void releaseLock(ref FileDescriptor fd) {
if (fd != FileDescriptor.none) {
close(cast(int) fd);
fd = FileDescriptor.none;
}
}
version(WithRPC) {
// Register signal listeners
// FIXME: this does not work yet.
MessageRouter router = new MessageRouter();
MessagePattern sleepPattern = MessagePattern(loginPath, loginIFace, "PrepareForSleep", true);
MessagePattern shutdownPattern = MessagePattern(loginPath, loginIFace, "PrepareForShutdown", true);
router.setHandler!(void, bool)(sleepPattern, (bool active) {
logf("Preparing for sleep: %b", active);
if (active) {
m_client.notify("NotifySleep");
releaseLock(sleepLock);
} else {
m_client.notify("NotifyWakeup");
sleepLock = loginManager.Inhibit("sleep", appName, "Systeem op de hoogte brengen dat de ober gaat slapen", "delay").to!FileDescriptor;
}
});
router.setHandler!(void, bool)(shutdownPattern, (bool active) {
logf("Preparing for shutdown: %b", active);
if (active) {
m_client.notify("NotifyShutdown");
releaseLock(shutdownLock);
}
});
registerRouter(m_dbusCon, router);
// Take the sleep lock
sleepLock = loginManager.Inhibit("sleep", appName, "Systeem op de hoogte brengen dat de ober gaat slapen",
"delay").to!FileDescriptor;
shutdownLock = loginManager.Inhibit("shutdown", appName, "Systeem op de hoogte brengen dat de ober gaat sluiten",
"delay").to!FileDescriptor;
}
void block() {
if (inhibitLock != FileDescriptor.none) return;
Message mesg = loginManager.Inhibit("shutdown:sleep:idle:handle-suspend-key:handle-power-key", appName, "Er zijn spelers op de server", "block");
inhibitLock = mesg.to!FileDescriptor;
}
scope (exit) {
// Als we om een of andere redenen deze functie verlaten, laat het slot los!
releaseLock(inhibitLock);
releaseLock(sleepLock);
releaseLock(shutdownLock);
}
int checkCount = CHECK_COUNT;
while(m_keepRunning) {
// Check if we are already preventing shutdown
if (inhibitLock != FileDescriptor.none) {
// We are. Check if we can shutdown (as in, no players are on the server)
if (canShutdown()) {
if (checkCount == 1) {
// Release the lock
releaseLock(inhibitLock);
info("Stop preventing shutdown");
} else {
// Check 1? more time
checkCount--;
tracef("Checking %d more time(s)", checkCount);
}
} else {
// We cannot shut down. Reset the check counter.
checkCount = CHECK_COUNT;
tracef("Still players out there. Keeping lock");
}
} else if (!canShutdown()) {
try {
block();
} catch(DBusException e) {
warning("Could not take lock and prevent sleep/shutdown: ", e);
}
info("Start preventing shutdown");
} else {
trace("Nothing to do");
}
sleep(seconds(m_waitingTime));
}
});
int exitCode = runEventLoop();
// cleanup
info("Cleanup");
return exitCode;
}
/**
* Checks if the system can shut down.
*/
bool canShutdown() {
auto result = execute(["ss", "-H", "--query=tcp", "state", "established", "sport", "=", ":minecraft"]);
return result.output.splitLines().length == 0;
}
}
int main(string[] args) {
scope(failure) stderr.writefln("GEBRUIK: %s WACHTTIJD KLANTADRES", args[0]);
if (args.length < 3) {
stderr.writefln("GEBRUIK: %s WACHTTIJD KLANTADRES", args[0]);
return -1;
}
int waitingTime = to!int(args[1]);
return Toep(waitingTime, args[2]).exec();
}

View file

@ -0,0 +1,134 @@
import core.atomic;
import std.array;
import std.concurrency : receiveOnly;
import std.exception;
import std.experimental.logger;
import std.traits;
import std.typecons;
import msgpack;
import vibe.core.concurrency;
import vibe.core.core;
import vibe.core.net;
import vibe.core.task;
import protocol;
/**
* MessagePack RPC client
*/
class Client {
private:
TCPConnection m_connection;
shared int m_nextRequestId = 0;
/// Map of sent request, mapping the requestId to a future.
Tid[int] m_requests;
public:
this(TCPConnection connection) {
this.m_connection = connection;
runTask(&receiveLoop);
}
/**
* Notifies the server, not waiting for a response. Although it waits for the request to be sent
*
* params:
* method = The name of the method to be notified
* arguments = The arguments to pass to the server
*/
void notify(Args...)(string method, Args arguments) {
tracef("Notify '%s'", method);
auto pack = packer(Appender!(ubyte[])());
pack.beginArray(3).pack(MessageType.notify, method, pack.packArray(arguments));
sendData(pack.stream.data);
}
/**
* Calls a method on the other side, awaiting a response.
*
* The difference between this and notify(string, Args) is that call expects a result from the
* other endpoint and will actually wait for it.
*/
R call(R, Args...)(string method, Args arguments) {
R res = sendRequest!R(method, arguments).getResult();
return res;
}
/**
* Calls a method on the other endpoint, but will not wait for a result. Instead, a Future!R is
* returned.
*/
Future!R callAsync(R, Args...)(string method, Args arguments) {
return sendRequest!R(method, arguments);
}
private:
void receiveLoop() {
StreamingUnpacker unpacker = StreamingUnpacker([]);
while(m_connection.connected) {
m_connection.waitForData();
ubyte[] buf = new ubyte[m_connection.leastSize];
m_connection.read(buf);
unpacker.feed(buf);
foreach(ref message; unpacker) {
enforce!ProtocolException(message.length == 3 || message.length == 4, "Protocol error: incoming message size mismatch");
immutable uint messageType = message[0].as!uint;
switch(messageType) {
case MessageType.response:
enforce!ProtocolException(message.length == 4, "Protocol error: Response must be of size 4");
immutable error = message[2];
immutable result = message[3];
onResponse(message[1].as!int, error, result);
break;
default:
tracef("Received unhandled messageType %s", cast(MessageType) messageType);
break;
}
}
}
}
void onResponse(int id, immutable Value error, immutable Value result) {
if (id in m_requests) {
tracef("Reply for %d; Error: %s; Result: %s", id, error.type, result.type);
send(m_requests[id], error, result);
m_requests.remove(id);
} else {
warningf("No task for id %d", id);
}
}
void sendData(T)(T data) {
m_connection.write(data);
m_connection.flush();
}
/**
* Sends a request and returns a future containing the result.
*/
Future!R sendRequest(R, Args...)(string method, Args arguments) {
tracef("Call '%s'", method);
auto pack = packer(Appender!(ubyte[])());
immutable int requestId = atomicFetchAdd(m_nextRequestId, 1);
pack.beginArray(4)
.pack(MessageType.request, requestId, method)
.packArray(arguments);
sendData(pack.stream.data);
return async(delegate R() {
m_requests[requestId] = Task.getThis().tid;
// error result
Tuple!(immutable Value, immutable Value) reply = receiveOnly!(immutable Value, immutable Value);
if (reply[0].type != Value.Type.nil) {
throw new RPCException(reply[0]);
}
Value result = reply[1];
return result.as!R;
});
}
}

View file

@ -0,0 +1,3 @@
module msgpackrpc;
public import client;
public import server;

View file

@ -0,0 +1,32 @@
import std.exception;
import msgpack;
enum MessageType {
request = 0,
response = 1,
notify = 2
}
/**
* Thrown when the wire protocol could not be parsed.
*/
class ProtocolException : Exception {
mixin basicExceptionCtors;
}
/**
* Thrown when the other endpoint reports an error.
*/
class RPCException : Exception {
private:
Value m_value;
public:
this(Value value, string file = __FILE__, int line = __LINE__, Throwable next = null) {
super(value.as!string, file, line, next);
this.m_value = value;
}
@property Value value() { return m_value; }
}

View file