AIUO/ober/source/app.d
2021-05-16 20:55:26 +02:00

272 lines
7.8 KiB
D

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;
version(WithRPC) import msgpackrpc;
string appName = "Automatisch In- en Uitschakelen Ober";
extern (C) void close(int fileNo);
/**
* Represententeerd de toepassing
*/
struct Toep {
public:
/**
* Tijd die tussen het schakelen van GebruikerActief -> SpelerActief en SpelerActief -> Uit
* moet zitten.
*/
immutable float m_waitingTime;
/// Aantal keer dat moet worden gecontroleerd of er een gebruiker actief is.
immutable int CHECK_COUNT = 4;
Connection m_dbusCon;
bool m_keepRunning = true;
enum Staat {
Inactief,
/// Een (Unix-)gebruiker is actief op het rekentuig
GebruikersActief,
/// Een speler is actief op de mijnvervaardigingsober
SpelersActief,
/// Het rekentuig moet uit
Uit
}
this(float waitingTime) {
this.m_waitingTime = waitingTime / CHECK_COUNT;
this.m_dbusCon = connectToBus(DBusBusType.DBUS_BUS_SYSTEM);
}
version(WithRPC) {
Client m_client;
immutable string m_clientAddress;
immutable ushort PORT = 18_002;
this(float waitingTime, string clientAddress) {
this.m_waitingTime = waitingTime / CHECK_COUNT;
this.m_clientAddress = clientAddress;
this.m_dbusCon = connectToBus(DBusBusType.DBUS_BUS_SYSTEM);
}
}
/**
* Voer de toepassing uit en treed de gebeurtenissenlus binnen.
*
* return: Afsluitcode die geretourneerd moet worden binnen main()
*/
int exec() {
info ("Gebruikers actief:", gebruikersActief());
info ("Spelers actief:", spelersActief());
version(WithRPC) {
TCPConnection conn = connectTCP(m_clientAddress, PORT);
Duration sleepTime = 500.msecs;
while(!conn) {
infof("Niet verbonden, opnieuw proberen in %s", sleepTime);
sleep(sleepTime);
conn = connectTCP(m_clientAddress, PORT);
}
info("Verbonden!");
m_client = new Client(conn);
writeln("Antwoord ontvangen: '", m_client.call!(string)("GetBootReason"), "'");
}
// Start de D-Bus-gebeurtenissenlus.
runTask({
import vibe.core.core : yield;
while(m_dbusCon.tick() && m_keepRunning) {
yield();
}
});
// Start de gebeurtenissenlus voor de hoofdlogica.
auto taak = 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"));
/// Laat een slot los.
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 blokkeerAfsluiten() {
if (inhibitLock != FileDescriptor.none) return;
Message mesg = loginManager.Inhibit("shutdown:sleep:idle:handle-suspend-key", appName, "Er zijn spelers op de ober", "block");
inhibitLock = mesg.to!FileDescriptor;
}
scope (exit) {
// Als we om een of andere redenen deze functie verlaten, laat het slot los!
releaseLock(inhibitLock);
version(WithRPC) {
/+releaseLock(sleepLock);
releaseLock(shutdownLock);+/
}
}
// Teller die bijhoudt wanneer we bepaalde van bepaalde staten mogen wisselen.
Staat staat = Staat.Inactief;
int teller = CHECK_COUNT;
while(m_keepRunning) {
Staat oudeStaat = staat;
sleep(seconds(cast(long) (m_waitingTime * 60)));
// Staattransities die vanuit elke staat kunnen plaatsvinden.
if (spelersActief()) {
staat = Staat.SpelersActief;
}
final switch(staat) {
case Staat.Inactief:
if (spelersActief()) {
staat = Staat.SpelersActief;
teller = 3;
} else if (gebruikersActief) {
staat = Staat.GebruikersActief;
teller = 3;
} else if (--teller <= 0) {
sluitAf();
staat = Staat.Uit;
}
break;
case Staat.GebruikersActief:
if (spelersActief()) {
teller = 3;
staat = Staat.SpelersActief;
} else if (gebruikersActief()) {
teller = 3;
} else if (--teller <= 0) {
staat = Staat.Uit;
sluitAf();
}
break;
case Staat.SpelersActief:
try {
blokkeerAfsluiten();
} catch(DBusException e) {
warning("Kon afsluitslot niet verkrijgen: ", e);
}
if (spelersActief()) {
teller = 3;
} else if (--teller <= 0) {
releaseLock(inhibitLock);
staat = Staat.GebruikersActief;
teller = 3;
}
break;
case Staat.Uit:
info("We gaan dit programma afsluiten");
m_keepRunning = false;
exitEventLoop();
return;
}
tracef("Oude -> nieuwe staat: %s -> %s (teller: %d)", oudeStaat, staat, teller);
}
});
int exitCode = runEventLoop();
// Rommel opruimen
info("Rommel opruimen.");
return exitCode;
}
/**
* Kijkt of we het afsluiten moeten blokkeren, oftewel, of we naar de SpelerActief-staat moeten.
*/
bool spelersActief() {
// Kijk of er verbindingen actief zijn naar poort ":minecraft"
auto resultaat = execute(["ss", "-H", "--query=tcp", "state", "established", "sport", "=", ":minecraft"]);
return resultaat.output.splitLines().length != 0;
}
/**
* Kijkt of we direct af kunnen sluiten, oftewel dat we naar de Uit-staat mogen.
*/
bool gebruikersActief() {
auto resultaat = execute(["who"]);
return resultaat.output.splitLines().length != 0;
}
void sluitAf() {
version(WithRPC) m_client.notify("NotifyShutdown");
execute(["shutdown", "+1", "Systeem gaat afsluiten vanwege inactiviteit"]);
}
}
int main(string[] args) {
version(WithRPC) {
scope(failure) stderr.writefln("GEBRUIK: %s WACHTTIJD KLANTADRES", args[0]);
if (args.length < 3) {
stderr.writefln("GEBRUIK: %s WACHTTIJD KLANTADRES", args[0]);
return -1;
}
} else {
scope(failure) stderr.writefln("GEBRUIK: %s WACHTTIJD", args[0]);
if (args.length < 3) {
stderr.writefln("GEBRUIK: %s WACHTTIJD", args[0]);
return -1;
}
}
float waitingTime = to!float(args[1]);
version(WithRPC) {
return Toep(waitingTime, args[2]).exec();
} else {
return Toep(waitingTime).exec();
}
}