Browse Source

Add an API to set and rollback the user status

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Carl Schwan 2 years ago
parent
commit
2cb48f484b

+ 1 - 0
apps/user_status/composer/composer/autoload_classmap.php

@@ -30,6 +30,7 @@ return array(
     'OCA\\UserStatus\\Migration\\Version0001Date20200602134824' => $baseDir . '/../lib/Migration/Version0001Date20200602134824.php',
     'OCA\\UserStatus\\Migration\\Version0002Date20200902144824' => $baseDir . '/../lib/Migration/Version0002Date20200902144824.php',
     'OCA\\UserStatus\\Migration\\Version1000Date20201111130204' => $baseDir . '/../lib/Migration/Version1000Date20201111130204.php',
+    'OCA\\UserStatus\\Migration\\Version2301Date20210809144824' => $baseDir . '/../lib/Migration/Version2301Date20210809144824.php',
     'OCA\\UserStatus\\Service\\EmojiService' => $baseDir . '/../lib/Service/EmojiService.php',
     'OCA\\UserStatus\\Service\\JSDataService' => $baseDir . '/../lib/Service/JSDataService.php',
     'OCA\\UserStatus\\Service\\PredefinedStatusService' => $baseDir . '/../lib/Service/PredefinedStatusService.php',

+ 1 - 0
apps/user_status/composer/composer/autoload_static.php

@@ -45,6 +45,7 @@ class ComposerStaticInitUserStatus
         'OCA\\UserStatus\\Migration\\Version0001Date20200602134824' => __DIR__ . '/..' . '/../lib/Migration/Version0001Date20200602134824.php',
         'OCA\\UserStatus\\Migration\\Version0002Date20200902144824' => __DIR__ . '/..' . '/../lib/Migration/Version0002Date20200902144824.php',
         'OCA\\UserStatus\\Migration\\Version1000Date20201111130204' => __DIR__ . '/..' . '/../lib/Migration/Version1000Date20201111130204.php',
+        'OCA\\UserStatus\\Migration\\Version2301Date20210809144824' => __DIR__ . '/..' . '/../lib/Migration/Version2301Date20210809144824.php',
         'OCA\\UserStatus\\Service\\EmojiService' => __DIR__ . '/..' . '/../lib/Service/EmojiService.php',
         'OCA\\UserStatus\\Service\\JSDataService' => __DIR__ . '/..' . '/../lib/Service/JSDataService.php',
         'OCA\\UserStatus\\Service\\PredefinedStatusService' => __DIR__ . '/..' . '/../lib/Service/PredefinedStatusService.php',

+ 16 - 1
apps/user_status/lib/Connector/UserStatusProvider.php

@@ -27,8 +27,9 @@ namespace OCA\UserStatus\Connector;
 
 use OCA\UserStatus\Service\StatusService;
 use OCP\UserStatus\IProvider;
+use OC\UserStatus\ISettableProvider;
 
-class UserStatusProvider implements IProvider {
+class UserStatusProvider implements IProvider, ISettableProvider {
 
 	/** @var StatusService */
 	private $service;
@@ -55,4 +56,18 @@ class UserStatusProvider implements IProvider {
 
 		return $userStatuses;
 	}
+
+	public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup = false): void {
+		if ($createBackup) {
+			if ($this->service->backupCurrentStatus($userId) === false) {
+				return; // Already a status set automatically => abort.
+			}
+		}
+		$this->service->setStatus($userId, $status, null, true);
+		$this->service->setPredefinedMessage($userId, $messageId, null);
+	}
+
+	public function revertUserStatus(string $userId, string $messageId, string $status): void {
+		$this->service->revertUserStatus($userId, $messageId, $status);
+	}
 }

+ 4 - 1
apps/user_status/lib/Controller/PredefinedStatusController.php

@@ -60,6 +60,9 @@ class PredefinedStatusController extends OCSController {
 	 * @return DataResponse
 	 */
 	public function findAll():DataResponse {
-		return new DataResponse($this->predefinedStatusService->getDefaultStatuses());
+		// Filtering out the invisible one, that should only be set by API
+		return new DataResponse(array_filter($this->predefinedStatusService->getDefaultStatuses(), function (array $status) {
+			return !array_key_exists('visible', $status) || $status['visible'] === true;
+		}));
 	}
 }

+ 6 - 0
apps/user_status/lib/Db/UserStatus.php

@@ -50,6 +50,8 @@ use OCP\AppFramework\Db\Entity;
  * @method void setCustomMessage(string|null $customMessage)
  * @method int getClearAt()
  * @method void setClearAt(int|null $clearAt)
+ * @method setIsBackup(bool $true): void
+ * @method getIsBackup(): bool
  */
 class UserStatus extends Entity {
 
@@ -77,6 +79,9 @@ class UserStatus extends Entity {
 	/** @var int|null */
 	public $clearAt;
 
+	/** @var bool $isBackup */
+	public $isBackup;
+
 	public function __construct() {
 		$this->addType('userId', 'string');
 		$this->addType('status', 'string');
@@ -86,5 +91,6 @@ class UserStatus extends Entity {
 		$this->addType('customIcon', 'string');
 		$this->addType('customMessage', 'string');
 		$this->addType('clearAt', 'int');
+		$this->addType('isBackup', 'boolean');
 	}
 }

+ 3 - 2
apps/user_status/lib/Db/UserStatusMapper.php

@@ -102,12 +102,13 @@ class UserStatusMapper extends QBMapper {
 	 * @return UserStatus
 	 * @throws \OCP\AppFramework\Db\DoesNotExistException
 	 */
-	public function findByUserId(string $userId):UserStatus {
+	public function findByUserId(string $userId, bool $isBackup = false):UserStatus {
 		$qb = $this->db->getQueryBuilder();
 		$qb
 			->select('*')
 			->from($this->tableName)
-			->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
+			->where($qb->expr()->eq('user_id', $qb->createNamedParameter($isBackup ? '_' . $userId : $userId, IQueryBuilder::PARAM_STR)))
+			->andWhere($qb->expr()->eq('is_backup', $qb->createNamedParameter($isBackup, IQueryBuilder::PARAM_BOOL)));
 
 		return $this->findEntity($qb);
 	}

+ 58 - 0
apps/user_status/lib/Migration/Version2301Date20210809144824.php

@@ -0,0 +1,58 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2021 Carl Schwan <carl@carlschwan.eu>
+ *
+ * @author Carl Schwan <carl@carlschwan.eu>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\UserStatus\Migration;
+
+use OCP\DB\ISchemaWrapper;
+use OCP\DB\Types;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+/**
+ * @package OCA\UserStatus\Migration
+ */
+class Version2301Date20210809144824 extends SimpleMigrationStep {
+
+	/**
+	 * @param IOutput $output
+	 * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+	 * @param array $options
+	 * @return null|ISchemaWrapper
+	 * @since 23.0.0
+	 */
+	public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
+		/** @var ISchemaWrapper $schema */
+		$schema = $schemaClosure();
+
+		$statusTable = $schema->getTable('user_status');
+
+		$statusTable->addColumn('is_backup', Types::BOOLEAN, [
+			'notnull' => false,
+			'default' => false,
+		]);
+
+		return $schema;
+	}
+}

+ 15 - 0
apps/user_status/lib/Service/PredefinedStatusService.php

@@ -41,6 +41,7 @@ class PredefinedStatusService {
 	private const SICK_LEAVE = 'sick-leave';
 	private const VACATIONING = 'vacationing';
 	private const REMOTE_WORK = 'remote-work';
+	public const CALL = 'call';
 
 	/** @var IL10N */
 	private $l10n;
@@ -101,6 +102,13 @@ class PredefinedStatusService {
 				'message' => $this->getTranslatedStatusForId(self::VACATIONING),
 				'clearAt' => null,
 			],
+			[
+				'id' => self::CALL,
+				'icon' => '💬',
+				'message' => $this->getTranslatedStatusForId(self::CALL),
+				'clearAt' => null,
+				'visible' => false,
+			],
 		];
 	}
 
@@ -139,6 +147,9 @@ class PredefinedStatusService {
 			case self::REMOTE_WORK:
 				return '🏡';
 
+			case self::CALL:
+				return '💬';
+
 			default:
 				return null;
 		}
@@ -166,6 +177,9 @@ class PredefinedStatusService {
 			case self::REMOTE_WORK:
 				return $this->l10n->t('Working remotely');
 
+			case self::CALL:
+				return $this->l10n->t('In a call');
+
 			default:
 				return null;
 		}
@@ -182,6 +196,7 @@ class PredefinedStatusService {
 			self::SICK_LEAVE,
 			self::VACATIONING,
 			self::REMOTE_WORK,
+			self::CALL,
 		], true);
 	}
 }

+ 63 - 3
apps/user_status/lib/Service/StatusService.php

@@ -64,7 +64,7 @@ class StatusService {
 		IUserStatus::AWAY,
 		IUserStatus::DND,
 		IUserStatus::INVISIBLE,
-		IUserStatus::OFFLINE
+		IUserStatus::OFFLINE,
 	];
 
 	/**
@@ -172,6 +172,7 @@ class StatusService {
 		$userStatus->setStatus($status);
 		$userStatus->setStatusTimestamp($statusTimestamp);
 		$userStatus->setIsUserDefined($isUserDefined);
+		$userStatus->setIsBackup(false);
 
 		if ($userStatus->getId() === null) {
 			return $this->mapper->insert($userStatus);
@@ -316,9 +317,9 @@ class StatusService {
 	 * @param string $userId
 	 * @return bool
 	 */
-	public function removeUserStatus(string $userId): bool {
+	public function removeUserStatus(string $userId, bool $isBackup = false): bool {
 		try {
-			$userStatus = $this->mapper->findByUserId($userId);
+			$userStatus = $this->mapper->findByUserId($userId, $isBackup);
 		} catch (DoesNotExistException $ex) {
 			// if there is no status to remove, just return
 			return false;
@@ -390,4 +391,63 @@ class StatusService {
 			$status->setCustomIcon($predefinedMessage['icon']);
 		}
 	}
+
+	/**
+	 * @return bool false iff there is already a backup. In this case abort the procedure.
+	 */
+	public function backupCurrentStatus(string $userId): bool {
+		try {
+			$this->mapper->findByUserId($userId, true);
+			return false;
+		} catch (DoesNotExistException $ex) {
+			// No backup already existing => Good
+		}
+
+		try {
+			$userStatus = $this->mapper->findByUserId($userId);
+		} catch (DoesNotExistException $ex) {
+			// if there is no status to backup, just return
+			return true;
+		}
+
+		$userStatus->setIsBackup(true);
+		// Prefix user account with an underscore because user_id is marked as unique
+		// in the table. Starting an username with an underscore is not allowed so this
+		// shouldn't create any trouble.
+		$userStatus->setUserId('_' . $userStatus->getUserId());
+		$this->mapper->update($userStatus);
+		return true;
+	}
+
+	public function revertUserStatus(string $userId, string $messageId, string $status): void {
+		try {
+			/** @var UserStatus $userStatus */
+			$backupUserStatus = $this->mapper->findByUserId($userId, true);
+		} catch (DoesNotExistException $ex) {
+			// No backup, just move back to available
+			try {
+				$userStatus = $this->mapper->findByUserId($userId);
+			} catch (DoesNotExistException $ex) {
+				// No backup nor current status => ignore
+				return;
+			}
+			$this->cleanStatus($userStatus);
+			$this->cleanStatusMessage($userStatus);
+			return;
+		}
+		try {
+			$userStatus = $this->mapper->findByUserId($userId);
+			if ($userStatus->getMessageId() !== $messageId || $userStatus->getStatus() !== $status) {
+				// Another status is set automatically, do nothing
+				return;
+			}
+			$this->removeUserStatus($userId);
+		} catch (DoesNotExistException $ex) {
+			// No current status => nothing to delete
+		}
+		$backupUserStatus->setIsBackup(false);
+		// Remove the underscore prefix added when creating the backup
+		$backupUserStatus->setUserId(substr($backupUserStatus->getUserId(), 1));
+		$this->mapper->update($backupUserStatus);
+	}
 }

+ 21 - 8
apps/user_status/tests/Unit/Service/PredefinedStatusServiceTest.php

@@ -47,14 +47,15 @@ class PredefinedStatusServiceTest extends TestCase {
 	}
 
 	public function testGetDefaultStatuses(): void {
-		$this->l10n->expects($this->exactly(5))
+		$this->l10n->expects($this->exactly(6))
 			->method('t')
 			->withConsecutive(
 				['In a meeting'],
 				['Commuting'],
 				['Working remotely'],
 				['Out sick'],
-				['Vacationing']
+				['Vacationing'],
+				['In a call'],
 			)
 			->willReturnArgument(0);
 
@@ -102,6 +103,13 @@ class PredefinedStatusServiceTest extends TestCase {
 				'message' => 'Vacationing',
 				'clearAt' => null,
 			],
+			[
+				'id' => 'call',
+				'icon' => '💬',
+				'message' => 'In a call',
+				'clearAt' => null,
+				'visible' => false,
+			],
 		], $actual);
 	}
 
@@ -126,6 +134,7 @@ class PredefinedStatusServiceTest extends TestCase {
 			['sick-leave', '🤒'],
 			['vacationing', '🌴'],
 			['remote-work', '🏡'],
+			['call', '💬'],
 			['unknown-id', null],
 		];
 	}
@@ -154,6 +163,7 @@ class PredefinedStatusServiceTest extends TestCase {
 			['sick-leave', 'Out sick'],
 			['vacationing', 'Vacationing'],
 			['remote-work', 'Working remotely'],
+			['call', 'In a call'],
 			['unknown-id', null],
 		];
 	}
@@ -179,28 +189,31 @@ class PredefinedStatusServiceTest extends TestCase {
 			['sick-leave', true],
 			['vacationing', true],
 			['remote-work', true],
+			['call', true],
 			['unknown-id', false],
 		];
 	}
 
 	public function testGetDefaultStatusById(): void {
-		$this->l10n->expects($this->exactly(5))
+		$this->l10n->expects($this->exactly(6))
 			->method('t')
 			->withConsecutive(
 				['In a meeting'],
 				['Commuting'],
 				['Working remotely'],
 				['Out sick'],
-				['Vacationing']
+				['Vacationing'],
+				['In a call'],
 			)
 			->willReturnArgument(0);
 
 		$this->assertEquals([
-			'id' => 'vacationing',
-			'icon' => '🌴',
-			'message' => 'Vacationing',
+			'id' => 'call',
+			'icon' => '💬',
+			'message' => 'In a call',
 			'clearAt' => null,
-		], $this->service->getDefaultStatusById('vacationing'));
+			'visible' => false,
+		], $this->service->getDefaultStatusById('call'));
 	}
 
 	public function testGetDefaultStatusByUnknownId(): void {

+ 49 - 0
apps/user_status/tests/Unit/Service/StatusServiceTest.php

@@ -665,4 +665,53 @@ class StatusServiceTest extends TestCase {
 
 		parent::invokePrivate($this->service, 'cleanStatus', [$status]);
 	}
+
+	public function testBackupWorkingHasBackupAlready() {
+		$status = new UserStatus();
+		$status->setStatus(IUserStatus::ONLINE);
+		$status->setStatusTimestamp(1337);
+		$status->setIsUserDefined(true);
+		$status->setMessageId('meeting');
+		$status->setUserId('john');
+		$status->setIsBackup(true);
+
+		$this->mapper->expects($this->once())
+			->method('findByUserId')
+			->with('john', true)
+			->willReturn($status);
+
+		$this->service->backupCurrentStatus('john');
+	}
+
+	public function testBackup() {
+		$currentStatus = new UserStatus();
+		$currentStatus->setStatus(IUserStatus::ONLINE);
+		$currentStatus->setStatusTimestamp(1337);
+		$currentStatus->setIsUserDefined(true);
+		$currentStatus->setMessageId('meeting');
+		$currentStatus->setUserId('john');
+
+		$this->mapper->expects($this->at(0))
+			->method('findByUserId')
+			->with('john', true)
+			->willThrowException(new DoesNotExistException(''));
+		$this->mapper->expects($this->at(1))
+			->method('findByUserId')
+			->with('john', false)
+			->willReturn($currentStatus);
+
+		$newBackupStatus = new UserStatus();
+		$newBackupStatus->setStatus(IUserStatus::ONLINE);
+		$newBackupStatus->setStatusTimestamp(1337);
+		$newBackupStatus->setIsUserDefined(true);
+		$newBackupStatus->setMessageId('meeting');
+		$newBackupStatus->setUserId('_john');
+		$newBackupStatus->setIsBackup(true);
+
+		$this->mapper->expects($this->once())
+			->method('update')
+			->with($newBackupStatus);
+
+		$this->service->backupCurrentStatus('john');
+	}
 }

+ 0 - 6
build/psalm-baseline.xml

@@ -4909,12 +4909,6 @@
       <code>setPassword</code>
     </UndefinedInterfaceMethod>
   </file>
-  <file src="lib/private/UserStatus/Manager.php">
-    <InvalidCatch occurrences="1"/>
-    <InvalidPropertyAssignmentValue occurrences="1">
-      <code>$class</code>
-    </InvalidPropertyAssignmentValue>
-  </file>
   <file src="lib/private/legacy/OC_API.php">
     <InvalidNullableReturnType occurrences="1">
       <code>int</code>

+ 1 - 0
lib/composer/composer/autoload_classmap.php

@@ -1453,6 +1453,7 @@ return array(
     'OC\\Updater\\ChangesMapper' => $baseDir . '/lib/private/Updater/ChangesMapper.php',
     'OC\\Updater\\ChangesResult' => $baseDir . '/lib/private/Updater/ChangesResult.php',
     'OC\\Updater\\VersionCheck' => $baseDir . '/lib/private/Updater/VersionCheck.php',
+    'OC\\UserStatus\\ISettableProvider' => $baseDir . '/lib/private/UserStatus/ISettableProvider.php',
     'OC\\UserStatus\\Manager' => $baseDir . '/lib/private/UserStatus/Manager.php',
     'OC\\User\\Backend' => $baseDir . '/lib/private/User/Backend.php',
     'OC\\User\\Database' => $baseDir . '/lib/private/User/Database.php',

+ 1 - 0
lib/composer/composer/autoload_static.php

@@ -1482,6 +1482,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OC\\Updater\\ChangesMapper' => __DIR__ . '/../../..' . '/lib/private/Updater/ChangesMapper.php',
         'OC\\Updater\\ChangesResult' => __DIR__ . '/../../..' . '/lib/private/Updater/ChangesResult.php',
         'OC\\Updater\\VersionCheck' => __DIR__ . '/../../..' . '/lib/private/Updater/VersionCheck.php',
+        'OC\\UserStatus\\ISettableProvider' => __DIR__ . '/../../..' . '/lib/private/UserStatus/ISettableProvider.php',
         'OC\\UserStatus\\Manager' => __DIR__ . '/../../..' . '/lib/private/UserStatus/Manager.php',
         'OC\\User\\Backend' => __DIR__ . '/../../..' . '/lib/private/User/Backend.php',
         'OC\\User\\Database' => __DIR__ . '/../../..' . '/lib/private/User/Database.php',

+ 55 - 0
lib/private/UserStatus/ISettableProvider.php

@@ -0,0 +1,55 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2021 Carl Schwan <carl@carlschwan.eu>
+ *
+ * @author Carl Schwan <carl@carlschwan.eu>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OC\UserStatus;
+
+use OCP\UserStatus\IProvider;
+
+/**
+ * Interface ISettableProvider
+ * @package OC\UserStatus
+ */
+interface ISettableProvider extends IProvider {
+	/**
+	 * Set a new status for the selected user.
+	 *
+	 * @param string $userId The user for which we want to update the status.
+	 * @param string $messageId The new message id.
+	 * @param string $status The new status.
+	 * @param bool $createBackup If true, this will store the old status so that it is possible to revert it later (e.g. after a call).
+	 */
+	public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup): void;
+
+	/**
+	 * Revert an automatically set user status. For example after leaving a call,
+	 * change back to the previously set status. If the user has already updated
+	 * their status, this method does nothing.
+	 *
+	 * @param string $userId The user for which we want to update the status.
+	 * @param string $messageId The expected current messageId.
+	 * @param string $status The expected current status.
+	 */
+	public function revertUserStatus(string $userId, string $messageId, string $status): void;
+}

+ 27 - 8
lib/private/UserStatus/Manager.php

@@ -25,21 +25,21 @@ declare(strict_types=1);
  */
 namespace OC\UserStatus;
 
-use OCP\ILogger;
 use OCP\IServerContainer;
 use OCP\UserStatus\IManager;
 use OCP\UserStatus\IProvider;
 use Psr\Container\ContainerExceptionInterface;
+use Psr\Log\LoggerInterface;
 
 class Manager implements IManager {
 
 	/** @var IServerContainer */
 	private $container;
 
-	/** @var ILogger */
+	/** @var LoggerInterface */
 	private $logger;
 
-	/** @var null */
+	/** @var class-string */
 	private $providerClass;
 
 	/** @var IProvider */
@@ -49,10 +49,10 @@ class Manager implements IManager {
 	 * Manager constructor.
 	 *
 	 * @param IServerContainer $container
-	 * @param ILogger $logger
+	 * @param LoggerInterface $logger
 	 */
 	public function __construct(IServerContainer $container,
-								ILogger $logger) {
+								LoggerInterface $logger) {
 		$this->container = $container;
 		$this->logger = $logger;
 	}
@@ -90,16 +90,35 @@ class Manager implements IManager {
 			return;
 		}
 
+		/**
+		 * @psalm-suppress InvalidCatch
+		 */
 		try {
 			$provider = $this->container->get($this->providerClass);
 		} catch (ContainerExceptionInterface $e) {
-			$this->logger->logException($e, [
-				'message' => 'Could not load user-status provider dynamically: ' . $e->getMessage(),
-				'level' => ILogger::ERROR,
+			$this->logger->error('Could not load user-status "' . $this->providerClass . '" provider dynamically: ' . $e->getMessage(), [
+				'exception' => $e,
 			]);
 			return;
 		}
 
 		$this->provider = $provider;
 	}
+
+	public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup = false): void {
+		$this->setupProvider();
+		if (!$this->provider || !($this->provider instanceof ISettableProvider)) {
+			return;
+		}
+
+		$this->provider->setUserStatus($userId, $messageId, $status, $createBackup);
+	}
+
+	public function revertUserStatus(string $userId, string $messageId, string $status): void {
+		$this->setupProvider();
+		if (!$this->provider || !($this->provider instanceof ISettableProvider)) {
+			return;
+		}
+		$this->provider->revertUserStatus($userId, $messageId, $status);
+	}
 }

+ 21 - 0
lib/public/UserStatus/IManager.php

@@ -40,4 +40,25 @@ interface IManager {
 	 * @since 20.0.0
 	 */
 	public function getUserStatuses(array $userIds):array;
+
+
+	/**
+	 * Set a new status for the selected user.
+	 *
+	 * @param string $userId The user for which we want to update the status.
+	 * @param string $messageId The id of the predefined message.
+	 * @param bool $createBackup If true, this will store the old status so that it is possible to revert it later (e.g. after a call).
+	 * @since 23.0.0
+	 */
+	public function setUserStatus(string $userId, string $messageId, string $status, bool $createBackup = false): void;
+
+	/**
+	 * Revert an automatically set user status. For example after leaving a call,
+	 * change back to the previously set status.
+	 *
+	 * @param string $userId The user for which we want to update the status.
+	 * @param string $messageId The expected current messageId. If the user has already updated their status, this method does nothing.
+	 * @since 23.0.0
+	 */
+	public function revertUserStatus(string $userId, string $messageId, string $status): void;
 }

+ 1 - 1
lib/public/UserStatus/IProvider.php

@@ -26,7 +26,7 @@ declare(strict_types=1);
 namespace OCP\UserStatus;
 
 /**
- * Interface IManager
+ * Interface IProvider
  *
  * @since 20.0.0
  */