Implemented templates and general improvements

This commit is contained in:
Maximilian Giller 2023-03-08 00:09:38 +01:00
parent d476d77340
commit 2ce66f0a56
7 changed files with 304 additions and 50 deletions

View file

@ -8,11 +8,17 @@ The status can only be updated with a valid secret key. The current status is st
## Setup ## Setup
Make sure the config file contains a good key, that the key is usable in a GET request and that the specified storage Make sure the config file contains a good key, that the key is usable in a GET request and that the specified storage
file is writable. files are writable.
The storage file is created if it does not exist. Since the storage file does not contain any sensitive information, it The storage files are created if they do not exist. Since the activity file does not contain any sensitive information,
it
can be made publicly accessible. At least it contains nothing that is not already available over the status itself. can be made publicly accessible. At least it contains nothing that is not already available over the status itself.
The templates file can also be publicly accessible. When it is first created, default templates are added, which are
specified in `Templates.loadDefaultTemplates()`. The templates can be modified or new ones can be added. The templates
are stored as json objects. It is recommended to let it create the templates file on the first request, and then go in
and modify it to your liking.
## Usage ## Usage
### Get status ### Get status
@ -31,11 +37,28 @@ your liking.
Here is an overview: Here is an overview:
| Parameter | Description | Example | | Parameter | Description | Example |
|---------------|-------------------------------------------------------------------|-----------------------------------------------------| |--------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------|
| `title` | The title of the status. | `title=Working` | | `title` | The title of the status. | `title=Working` |
| `description` | A description of the status. | `description=Making progress on personal projects.` | | `description` | A description of the status. | `description=Making progress on personal projects.` |
| `duration` | The expected duration of the status in seconds. | `duration=3600` | | `expectedDuration` | The expected duration of the status in seconds. | `duration=3600` |
| `available` | Whether you are available to other people. | `available=0` | | `available` | Whether you are available to other people. | `available=0` |
| `working` | Whether you are working, as opposed to having some personal time. | `working=1` | | `working` | Whether you are working, as opposed to having some personal time. | `working=1` |
| `template` | If a valid template id is specified, the template is applied to the activity. If other parameters are set, they will overwrite the template parameters. | `template=work` |
## Templates
Templates are a way to easily set often used status. They can either be specified as `template` parameter for specific
activity, or using simple rules and automations.
A template overwrites all parameters of an activity, only the `startTime` and `template` parameters are preserved. If
the template is applied via the `template` paramter, other specified parameters will overwrite the template parameters.
### Rules
Rules are checked every time the status is requested. If all rules of a template match, the template is applied. Each
template has a priority. The valid template with the highest priority is applied.
| Rule | Description | Further parameters |
|---------------------------|-----------------------------------------------------------------------------------|------------------------------------------------------|
| `triggerOnlyOnEmptyTitle` | Only apply the template if the title is empty. | |
| `triggerAfterTimeout` | Applies the template if the current activity is older than the specified timeout. | `timeout` specifies the required timeout in seconds. |

View file

@ -3,7 +3,7 @@
class Activity class Activity
{ {
public int $startTime; // Unix timestamp in seconds public int $startTime; // Unix timestamp in seconds
public int $expectedDuration; // In seconds, 0 if open-ended public int $expectedDuration; // Expected duration in seconds, 0 if open-ended
public string $title; public string $title;
public string $description; // Additional information, but might be kept empty public string $description; // Additional information, but might be kept empty
@ -11,6 +11,23 @@ class Activity
public bool $available; // Am I available for requests and to talk? public bool $available; // Am I available for requests and to talk?
public bool $working; // Am I working on something? public bool $working; // Am I working on something?
public string $template; // ID of the template that is supposed to be applied to this activity
public function __construct()
{
$this->startTime = time();
$this->expectedDuration = 0;
$this->title = "";
$this->description = "";
$this->available = false;
$this->working = false;
$this->template = "";
}
public function initialLoad(): void
{
}
public function getCurrentDuration(): int public function getCurrentDuration(): int
{ {
return time() - $this->startTime; return time() - $this->startTime;

View file

@ -1,4 +1,5 @@
<?php <?php
$SECRET_KEY = "ENTER_SECRET_HERE"; $SECRET_KEY = "ENTER_SECRET_HERE";
$STORAGE_FILE = "last_activity.json"; $ACTIVITY_FILE = "last_activity.json";
$TEMPLATES_FILE = "templates.json";

View file

@ -4,6 +4,7 @@ function respondWithJson($data): void
{ {
header('Content-Type: application/json'); header('Content-Type: application/json');
echo json_encode($data); echo json_encode($data);
die();
} }
function respondAndDie(string $message): void function respondAndDie(string $message): void

View file

@ -1,47 +1,74 @@
<?php <?php
// Temporary error reporting
error_reporting(E_ALL);
ini_set('display_errors', 1);
require_once("config.php"); require_once("config.php");
require_once("storage.php"); require_once("storage.php");
require_once("activity.php"); require_once("activity.php");
require_once("http.php"); require_once("http.php");
function loadGivenParameters(Activity $activity): void
{
if (isset($_GET["title"])) {
$activity->title = $_GET["title"];
}
if (isset($_GET["description"])) {
$activity->description = $_GET["description"];
}
if (isset($_GET["duration"])) {
$activity->expectedDuration = (int)$_GET["duration"];
}
if (isset($_GET["available"])) {
$activity->available = filter_var($_GET["available"], FILTER_VALIDATE_BOOLEAN);
}
if (isset($_GET["working"])) {
$activity->working = filter_var($_GET["working"], FILTER_VALIDATE_BOOLEAN);
}
if (isset($_GET["template"])) {
$activity->template = $_GET["template"];
}
}
function handleTemplate(Activity $activity)
{
// Load and find template
try {
$templates = loadTemplates();
} catch (Exception $e) {
respondAndDie($e->getMessage());
}
$template = $templates->findValidTemplate($activity);
if ($template == null) {
// No applicable template found
return;
}
// Apply template
$template->applyTo($activity);
// Overwrite parameters if template was specified on activity
if ($activity->template != "") {
loadGivenParameters($activity);
}
}
// If no secret key is provided, respond with the current activity // If no secret key is provided, respond with the current activity
global $SECRET_KEY; global $SECRET_KEY;
if (!isset($_GET["secret"]) || $_GET["secret"] != $SECRET_KEY) { if (!isset($_GET["secret"]) || $_GET["secret"] != $SECRET_KEY) {
// Respond with the current activity // Respond with the current activity
try {
$activity = loadActivity(); $activity = loadActivity();
} catch (Exception $e) {
respondAndDie($e->getMessage());
}
handleTemplate($activity);
respondWithJson($activity->getPublicData()); respondWithJson($activity->getPublicData());
} }
// Update activity // Update activity
$activity = new Activity(); $activity = new Activity();
$activity->startTime = time(); loadGivenParameters($activity);
if (isset($_GET["title"])) {
$activity->title = $_GET["title"];
} else {
$activity->title = "Awake";
}
if (isset($_GET["description"])) {
$activity->description = $_GET["description"];
} else {
$activity->description = "";
}
if (isset($_GET["duration"])) {
$activity->expectedDuration = (int)$_GET["duration"];
} else {
$activity->expectedDuration = 0;
}
if (isset($_GET["available"])) {
$activity->available = filter_var($_GET["available"], FILTER_VALIDATE_BOOLEAN);
} else {
$activity->available = false;
}
if (isset($_GET["working"])) {
$activity->working = filter_var($_GET["working"], FILTER_VALIDATE_BOOLEAN);
} else {
$activity->working = false;
}
storeActivity($activity); storeActivity($activity);
respondAndDie("Activity updated"); respondAndDie("Activity updated");

View file

@ -1,19 +1,76 @@
<?php <?php
require_once("config.php"); require_once("config.php");
require_once("templates.php");
require_once("activity.php");
function storeActivity(Activity $activity): void function storeActivity(Activity $activity): void
{ {
global $STORAGE_FILE; global $ACTIVITY_FILE;
$file = fopen($STORAGE_FILE, "w"); storeObject($activity, $ACTIVITY_FILE);
fwrite($file, json_encode($activity));
fclose($file);
} }
function loadActivity(): Activity function loadActivity(): Activity
{ {
global $STORAGE_FILE; global $ACTIVITY_FILE;
$file = fopen($STORAGE_FILE, "r"); return loadObject($ACTIVITY_FILE, Activity::class);
$line = fgets($file); }
fclose($file);
return json_decode($line); function storeTemplates(Templates $templates): void
{
global $TEMPLATES_FILE;
storeObject($templates, $TEMPLATES_FILE);
}
function loadTemplates(): Templates
{
global $TEMPLATES_FILE;
if (!file_exists($TEMPLATES_FILE)) {
// Create initial template file
$templates = new Templates();
$templates->loadDefaultTemplates();
storeTemplates($templates);
return $templates;
}
return loadObject($TEMPLATES_FILE, Templates::class);
}
function storeObject(object $object, string $file): void
{
$file = fopen($file, "w");
fwrite($file, json_encode($object, JSON_PRETTY_PRINT));
fclose($file);
}
/**
* @throws ReflectionException
* @throws JsonException
*/
function loadObject(string $file, string $class): object
{
$json = file_get_contents($file);
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
return parseObject($data, $class);
}
/**
* @throws ReflectionException
*/
function parseObject(array $data, string $class): object
{
$reflection = new ReflectionClass($class);
$instance = $reflection->newInstanceWithoutConstructor();
foreach ($data as $property => $value) {
$propertyReflection = $reflection->getProperty($property);
$propertyReflection->setAccessible(true);
$propertyReflection->setValue($instance, $value);
}
try {
$instance->initialLoad();
} catch (Exception $e) {
// Ignore
}
return $instance;
} }

128
src/templates.php Normal file
View file

@ -0,0 +1,128 @@
<?php
require_once("storage.php");
class Templates
{
public array $templates;
public function initialLoad()
{
foreach ($this->templates as $templateId => $template) {
$this->templates[$templateId] = parseObject($template, Template::class);
}
}
public function existsTemplate(string $templateId): bool
{
// Does key in array exist?
if (array_key_exists($templateId, $this->templates)) {
return true;
}
return false;
}
public function findValidTemplate(Activity $activity): ?Template
{
if ($activity->template != "") {
if ($this->existsTemplate($activity->template)) {
return $this->templates[$activity->template];
} else {
return null;
}
}
// Check for other validity conditions
$validTemplate = null;
foreach ($this->templates as $templateId => $template) {
if ($template->isValidFor($activity) && ($validTemplate == null || $template->priority > $validTemplate->priority)) {
$validTemplate = $template;
}
}
return $validTemplate;
}
public function loadDefaultTemplates(): void
{
$awakeTemplate = new Template();
$awakeTemplate->title = "Awake";
$awakeTemplate->description = "";
$awakeTemplate->expectedDuration = 0;
$awakeTemplate->available = false;
$awakeTemplate->working = false;
$awakeTemplate->priority = 0;
$awakeTemplate->triggerOnlyOnEmptyTitle = true;
$this->templates["awake"] = $awakeTemplate;
$sleepingTemplate = new Template();
$sleepingTemplate->title = "Sleeping";
$sleepingTemplate->description = "";
$sleepingTemplate->expectedDuration = 0;
$sleepingTemplate->available = false;
$sleepingTemplate->working = false;
$sleepingTemplate->priority = 10;
$sleepingTemplate->triggerOnlyOnEmptyTitle = true;
$sleepingTemplate->triggerAfterTimeout = true;
$sleepingTemplate->timeout = 60 * 60 * 4; // 4 hours
$this->templates["sleeping"] = $sleepingTemplate;
}
}
class Template
{
public string $title;
public string $description;
public int $expectedDuration;
public bool $available;
public bool $working;
// Meta template information
public int $priority; // Higher priority templates are applied first
public bool $triggerOnlyOnEmptyTitle; // If true, the template will be only applied if the activity title is empty
public bool $triggerAfterTimeout; // If true, the template will be applied after the timeout
public int $timeout; // Timeout for trigger in seconds
public function __construct()
{
$this->title = "";
$this->description = "";
$this->expectedDuration = 0;
$this->available = false;
$this->working = false;
$this->priority = 0;
$this->triggerOnlyOnEmptyTitle = false;
$this->triggerAfterTimeout = false;
$this->timeout = 0;
}
public function applyTo(Activity $activity): void
{
$activity->title = $this->title;
$activity->description = $this->description;
$activity->expectedDuration = $this->expectedDuration;
$activity->available = $this->available;
$activity->working = $this->working;
}
public function isValidFor(Activity $activity): bool
{
if ($this->triggerOnlyOnEmptyTitle && $activity->title != "") {
return false;
}
if ($this->triggerAfterTimeout && $activity->getCurrentDuration() < $this->timeout) {
return false;
}
return true;
}
public function initialLoad(): void
{
}
}