зеркало из https://github.com/nextcloud/spreed.git
Merge pull request #4196 from nextcloud/bugfix/noid/matterbridge
Bugfix/noid/matterbridge
This commit is contained in:
Коммит
6844da74a1
|
@ -16,7 +16,7 @@ And in the works for the [coming versions](https://github.com/nextcloud/spreed/m
|
||||||
|
|
||||||
]]></description>
|
]]></description>
|
||||||
|
|
||||||
<version>11.0.0-dev.1</version>
|
<version>11.0.0-dev.2</version>
|
||||||
<licence>agpl</licence>
|
<licence>agpl</licence>
|
||||||
|
|
||||||
<author>Daniel Calviño Sánchez</author>
|
<author>Daniel Calviño Sánchez</author>
|
||||||
|
|
|
@ -23,8 +23,10 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace OCA\Talk\BackgroundJob;
|
namespace OCA\Talk\BackgroundJob;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Utility\ITimeFactory;
|
||||||
use OCP\BackgroundJob\TimedJob;
|
use OCP\BackgroundJob\TimedJob;
|
||||||
use OCA\Talk\MatterbridgeManager;
|
use OCA\Talk\MatterbridgeManager;
|
||||||
|
use OCP\IConfig;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,24 +36,36 @@ use Psr\Log\LoggerInterface;
|
||||||
*/
|
*/
|
||||||
class CheckMatterbridges extends TimedJob {
|
class CheckMatterbridges extends TimedJob {
|
||||||
|
|
||||||
|
/** @var IConfig */
|
||||||
|
protected $serverConfig;
|
||||||
|
|
||||||
/** @var MatterbridgeManager */
|
/** @var MatterbridgeManager */
|
||||||
protected $bridgeManager;
|
protected $bridgeManager;
|
||||||
|
|
||||||
/** @var LoggerInterface */
|
/** @var LoggerInterface */
|
||||||
protected $logger;
|
protected $logger;
|
||||||
|
|
||||||
public function __construct(MatterbridgeManager $bridgeManager,
|
public function __construct(ITimeFactory $time,
|
||||||
|
IConfig $serverConfig,
|
||||||
|
MatterbridgeManager $bridgeManager,
|
||||||
LoggerInterface $logger) {
|
LoggerInterface $logger) {
|
||||||
|
parent::__construct($time);
|
||||||
|
|
||||||
// Every 15 minutes
|
// Every 15 minutes
|
||||||
$this->setInterval(60 * 15);
|
$this->setInterval(60 * 15);
|
||||||
|
|
||||||
|
$this->serverConfig = $serverConfig;
|
||||||
$this->bridgeManager = $bridgeManager;
|
$this->bridgeManager = $bridgeManager;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function run($argument): void {
|
protected function run($argument): void {
|
||||||
$this->bridgeManager->checkAllBridges();
|
if ($this->serverConfig->getAppValue('spreed', 'enable_matterbridge', '0') === '1') {
|
||||||
$this->bridgeManager->killZombieBridges();
|
$this->bridgeManager->checkAllBridges();
|
||||||
|
$this->bridgeManager->killZombieBridges();
|
||||||
|
} else {
|
||||||
|
$this->bridgeManager->stopAllBridges();
|
||||||
|
}
|
||||||
$this->logger->info('Checked if Matterbridge instances are running correctly.');
|
$this->logger->info('Checked if Matterbridge instances are running correctly.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,14 +23,11 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace OCA\Talk;
|
namespace OCA\Talk;
|
||||||
|
|
||||||
|
use OCA\Talk\Exceptions\RoomNotFoundException;
|
||||||
use OCP\IConfig;
|
use OCP\IConfig;
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
use OCP\IL10N;
|
|
||||||
use OCP\IUserManager;
|
use OCP\IUserManager;
|
||||||
use OCP\Files\IAppData;
|
|
||||||
use OCP\Files\NotFoundException;
|
|
||||||
use OCP\Files\SimpleFS\ISimpleFolder;
|
|
||||||
use OCP\IURLGenerator;
|
use OCP\IURLGenerator;
|
||||||
use OC\Authentication\Token\IProvider as IAuthTokenProvider;
|
use OC\Authentication\Token\IProvider as IAuthTokenProvider;
|
||||||
use OC\Authentication\Token\IToken;
|
use OC\Authentication\Token\IToken;
|
||||||
|
@ -50,24 +47,27 @@ class MatterbridgeManager {
|
||||||
private $db;
|
private $db;
|
||||||
/** @var IConfig */
|
/** @var IConfig */
|
||||||
private $config;
|
private $config;
|
||||||
/** @var IAppData */
|
/** @var IURLGenerator */
|
||||||
private $appData;
|
private $urlGenerator;
|
||||||
/** @var IL10N */
|
|
||||||
private $l;
|
|
||||||
/** @var IUserManager */
|
/** @var IUserManager */
|
||||||
private $userManager;
|
private $userManager;
|
||||||
|
/** @var Manager */
|
||||||
|
private $manager;
|
||||||
|
/** @var ChatManager */
|
||||||
|
private $chatManager;
|
||||||
/** @var IAuthTokenProvider */
|
/** @var IAuthTokenProvider */
|
||||||
private $tokenProvider;
|
private $tokenProvider;
|
||||||
/** @var ISecureRandom */
|
/** @var ISecureRandom */
|
||||||
private $random;
|
private $random;
|
||||||
/** @var ChatManager */
|
/** @var IAvatarManager */
|
||||||
private $chatManager;
|
private $avatarManager;
|
||||||
|
/** @var LoggerInterface */
|
||||||
|
private $logger;
|
||||||
/** @var ITimeFactory */
|
/** @var ITimeFactory */
|
||||||
private $timeFactory;
|
private $timeFactory;
|
||||||
|
|
||||||
public function __construct(IDBConnection $db,
|
public function __construct(IDBConnection $db,
|
||||||
IConfig $config,
|
IConfig $config,
|
||||||
IAppData $appData,
|
|
||||||
IURLGenerator $urlGenerator,
|
IURLGenerator $urlGenerator,
|
||||||
IUserManager $userManager,
|
IUserManager $userManager,
|
||||||
Manager $manager,
|
Manager $manager,
|
||||||
|
@ -76,20 +76,17 @@ class MatterbridgeManager {
|
||||||
ISecureRandom $random,
|
ISecureRandom $random,
|
||||||
IAvatarManager $avatarManager,
|
IAvatarManager $avatarManager,
|
||||||
LoggerInterface $logger,
|
LoggerInterface $logger,
|
||||||
IL10N $l,
|
|
||||||
ITimeFactory $timeFactory) {
|
ITimeFactory $timeFactory) {
|
||||||
$this->avatarManager = $avatarManager;
|
$this->avatarManager = $avatarManager;
|
||||||
$this->db = $db;
|
$this->db = $db;
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->urlGenerator = $urlGenerator;
|
$this->urlGenerator = $urlGenerator;
|
||||||
$this->appData = $appData;
|
|
||||||
$this->userManager = $userManager;
|
$this->userManager = $userManager;
|
||||||
$this->manager = $manager;
|
$this->manager = $manager;
|
||||||
$this->chatManager = $chatManager;
|
$this->chatManager = $chatManager;
|
||||||
$this->tokenProvider = $tokenProvider;
|
$this->tokenProvider = $tokenProvider;
|
||||||
$this->random = $random;
|
$this->random = $random;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->l = $l;
|
|
||||||
$this->timeFactory = $timeFactory;
|
$this->timeFactory = $timeFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,6 +134,7 @@ class MatterbridgeManager {
|
||||||
* Edit bridge information for a room
|
* Edit bridge information for a room
|
||||||
*
|
*
|
||||||
* @param Room $room the room
|
* @param Room $room the room
|
||||||
|
* @param string $userId
|
||||||
* @param bool $enabled desired state of the bridge
|
* @param bool $enabled desired state of the bridge
|
||||||
* @param array $parts parts of the bridge (what it connects to)
|
* @param array $parts parts of the bridge (what it connects to)
|
||||||
* @return array bridge state
|
* @return array bridge state
|
||||||
|
@ -150,7 +148,7 @@ class MatterbridgeManager {
|
||||||
}
|
}
|
||||||
$newBridge = [
|
$newBridge = [
|
||||||
'enabled' => $enabled,
|
'enabled' => $enabled,
|
||||||
'pid' => isset($currentBridge['pid']) ? $currentBridge['pid'] : 0,
|
'pid' => $currentBridge['pid'] ?? 0,
|
||||||
'parts' => $parts,
|
'parts' => $parts,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -164,7 +162,7 @@ class MatterbridgeManager {
|
||||||
$newBridge['pid'] = $pid;
|
$newBridge['pid'] = $pid;
|
||||||
|
|
||||||
// save config
|
// save config
|
||||||
$this->saveBridgeToDb($room, $newBridge);
|
$this->saveBridgeToDb($room->getId(), $newBridge);
|
||||||
|
|
||||||
$logContent = $this->getBridgeLog($room);
|
$logContent = $this->getBridgeLog($room);
|
||||||
return [
|
return [
|
||||||
|
@ -183,9 +181,9 @@ class MatterbridgeManager {
|
||||||
// first potentially kill the process
|
// first potentially kill the process
|
||||||
$currentBridge = $this->getBridgeOfRoom($room);
|
$currentBridge = $this->getBridgeOfRoom($room);
|
||||||
$currentBridge['enabled'] = false;
|
$currentBridge['enabled'] = false;
|
||||||
$this->checkBridgeProcess($token, $currentBridge);
|
$this->checkBridgeProcess($room, $currentBridge);
|
||||||
// then actually delete the config
|
// then actually delete the config
|
||||||
$bridgeJSON = $this->config->deleteAppValue('spreed', 'bridge_' . $token);
|
$this->config->deleteAppValue('spreed', 'bridge_' . $room->getToken());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,43 +192,51 @@ class MatterbridgeManager {
|
||||||
* For each room, check mattermost process respects desired state
|
* For each room, check mattermost process respects desired state
|
||||||
*/
|
*/
|
||||||
public function checkAllBridges(): void {
|
public function checkAllBridges(): void {
|
||||||
// TODO call this from time to time to make sure everything is running fine
|
$query = $this->db->getQueryBuilder();
|
||||||
$this->manager->forAllRooms(function ($room) {
|
$query->select('*')
|
||||||
if ($room->getType() === Room::GROUP_CALL || $room->getType() === Room::PUBLIC_CALL) {
|
->from('talk_bridges')
|
||||||
$this->checkBridge($room);
|
->where($query->expr()->eq('enabled', $query->createNamedParameter(1, IQueryBuilder::PARAM_INT)));
|
||||||
|
|
||||||
|
$result = $query->execute();
|
||||||
|
while ($row = $result->fetch()) {
|
||||||
|
$bridge = [
|
||||||
|
'enabled' => (bool) $row['enabled'],
|
||||||
|
'pid' => (int) $row['pid'],
|
||||||
|
'parts' => json_decode($row['json_values'], true),
|
||||||
|
];
|
||||||
|
try {
|
||||||
|
$room = $this->manager->getRoomById((int) $row['room_id']);
|
||||||
|
} catch (RoomNotFoundException $e) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
});
|
$this->checkBridge($room, $bridge);
|
||||||
|
}
|
||||||
|
$result->closeCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For one room, check mattermost process respects desired state
|
* For one room, check mattermost process respects desired state
|
||||||
* @param Room $room the room
|
* @param Room $room the room
|
||||||
|
* @param array|null $bridge
|
||||||
* @return int the bridge process ID
|
* @return int the bridge process ID
|
||||||
*/
|
*/
|
||||||
public function checkBridge(Room $room): int {
|
public function checkBridge(Room $room, ?array $bridge = null): int {
|
||||||
$bridge = $this->getBridgeOfRoom($room);
|
$bridge = $bridge ?: $this->getBridgeOfRoom($room);
|
||||||
$pid = $this->checkBridgeProcess($room, $bridge);
|
$pid = $this->checkBridgeProcess($room, $bridge);
|
||||||
if ($pid !== $bridge['pid']) {
|
if ($pid !== $bridge['pid']) {
|
||||||
// save the new PID if necessary
|
// save the new PID if necessary
|
||||||
$bridge['pid'] = $pid;
|
$bridge['pid'] = $pid;
|
||||||
$this->saveBridgeToDb($room, $bridge);
|
$this->saveBridgeToDb($room->getId(), $bridge);
|
||||||
}
|
}
|
||||||
return $pid;
|
return $pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getDataFolder(): ISimpleFolder {
|
|
||||||
try {
|
|
||||||
return $this->appData->getFolder('bridge');
|
|
||||||
} catch (NotFoundException $e) {
|
|
||||||
return $this->appData->newFolder('bridge');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edit the mattermost configuration file for one room
|
* Edit the mattermost configuration file for one room
|
||||||
* This method takes care of connecting the bridge to the Talk room with a bot user
|
* This method takes care of connecting the bridge to the Talk room with a bot user
|
||||||
*
|
*
|
||||||
* @param Room $room the room
|
* @param Room $room
|
||||||
|
* @param array $newBridge
|
||||||
*/
|
*/
|
||||||
private function editBridgeConfig(Room $room, array $newBridge): void {
|
private function editBridgeConfig(Room $room, array $newBridge): void {
|
||||||
// check bot user exists and is member of the room
|
// check bot user exists and is member of the room
|
||||||
|
@ -239,7 +245,7 @@ class MatterbridgeManager {
|
||||||
|
|
||||||
// TODO adapt that to use appData
|
// TODO adapt that to use appData
|
||||||
$configPath = sprintf('/tmp/bridge-%s.toml', $room->getToken());
|
$configPath = sprintf('/tmp/bridge-%s.toml', $room->getToken());
|
||||||
$configContent = $this->generateConfig($room, $newBridge);
|
$configContent = $this->generateConfig($newBridge);
|
||||||
file_put_contents($configPath, $configContent);
|
file_put_contents($configPath, $configContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,7 +264,7 @@ class MatterbridgeManager {
|
||||||
'password' => $botInfo['password'],
|
'password' => $botInfo['password'],
|
||||||
'channel' => $room->getToken(),
|
'channel' => $room->getToken(),
|
||||||
];
|
];
|
||||||
array_push($bridge['parts'], $localPart);
|
$bridge['parts'][] = $localPart;
|
||||||
return $bridge;
|
return $bridge;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,7 +282,7 @@ class MatterbridgeManager {
|
||||||
$botUserId = 'bridge-bot';
|
$botUserId = 'bridge-bot';
|
||||||
// check if user exists and create it if necessary
|
// check if user exists and create it if necessary
|
||||||
if (!$this->userManager->userExists($botUserId)) {
|
if (!$this->userManager->userExists($botUserId)) {
|
||||||
$pass = md5(strval(rand()));
|
$pass = md5((string)mt_rand());
|
||||||
$this->config->setAppValue('spreed', 'bridge_bot_password', $pass);
|
$this->config->setAppValue('spreed', 'bridge_bot_password', $pass);
|
||||||
$botUser = $this->userManager->createUser($botUserId, $pass);
|
$botUser = $this->userManager->createUser($botUserId, $pass);
|
||||||
// set avatar
|
// set avatar
|
||||||
|
@ -333,10 +339,10 @@ class MatterbridgeManager {
|
||||||
* Actually generate the matterbridge configuration file content for one bridge (one room)
|
* Actually generate the matterbridge configuration file content for one bridge (one room)
|
||||||
* It basically add a pair of sections for each part: authentication and target channel
|
* It basically add a pair of sections for each part: authentication and target channel
|
||||||
*
|
*
|
||||||
* @param Room $room the room
|
* @param array $bridge
|
||||||
* @return string config file content
|
* @return string config file content
|
||||||
*/
|
*/
|
||||||
private function generateConfig(Room $room, array $bridge): string {
|
private function generateConfig(array $bridge): string {
|
||||||
$content = '';
|
$content = '';
|
||||||
foreach ($bridge['parts'] as $k => $part) {
|
foreach ($bridge['parts'] as $k => $part) {
|
||||||
$type = $part['type'];
|
$type = $part['type'];
|
||||||
|
@ -396,8 +402,8 @@ class MatterbridgeManager {
|
||||||
$content .= ' RemoteNickFormat = "[{PROTOCOL}] <{NICK}> "' . "\n\n";
|
$content .= ' RemoteNickFormat = "[{PROTOCOL}] <{NICK}> "' . "\n\n";
|
||||||
} elseif ($type === 'slack') {
|
} elseif ($type === 'slack') {
|
||||||
// do not include # in channel
|
// do not include # in channel
|
||||||
if (preg_match('/^#/', $part['channel'])) {
|
if (strpos($part['channel'], '#') === 0) {
|
||||||
$bridge['parts'][$k]['channel'] = preg_replace('/^#+/', '', $part['channel']);
|
$bridge['parts'][$k]['channel'] = ltrim($part['channel'], '#');
|
||||||
}
|
}
|
||||||
$content .= sprintf('[%s.%s]', $type, $k) . "\n";
|
$content .= sprintf('[%s.%s]', $type, $k) . "\n";
|
||||||
$content .= sprintf(' Token = "%s"', $part['token']) . "\n";
|
$content .= sprintf(' Token = "%s"', $part['token']) . "\n";
|
||||||
|
@ -405,8 +411,8 @@ class MatterbridgeManager {
|
||||||
$content .= ' RemoteNickFormat = "[{PROTOCOL}] <{NICK}> "' . "\n\n";
|
$content .= ' RemoteNickFormat = "[{PROTOCOL}] <{NICK}> "' . "\n\n";
|
||||||
} elseif ($type === 'discord') {
|
} elseif ($type === 'discord') {
|
||||||
// do not include # in channel
|
// do not include # in channel
|
||||||
if (preg_match('/^#/', $part['channel'])) {
|
if (strpos($part['channel'], '#') === 0) {
|
||||||
$bridge['parts'][$k]['channel'] = preg_replace('/^#+/', '', $part['channel']);
|
$bridge['parts'][$k]['channel'] = ltrim($part['channel'], '#');
|
||||||
}
|
}
|
||||||
$content .= sprintf('[%s.%s]', $type, $k) . "\n";
|
$content .= sprintf('[%s.%s]', $type, $k) . "\n";
|
||||||
$content .= sprintf(' Token = "%s"', $part['token']) . "\n";
|
$content .= sprintf(' Token = "%s"', $part['token']) . "\n";
|
||||||
|
@ -493,6 +499,9 @@ class MatterbridgeManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the scheme from an URL and add port
|
* Remove the scheme from an URL and add port
|
||||||
|
*
|
||||||
|
* @param string $url
|
||||||
|
* @return string
|
||||||
*/
|
*/
|
||||||
private function cleanUrl(string $url): string {
|
private function cleanUrl(string $url): string {
|
||||||
$uo = parse_url($url);
|
$uo = parse_url($url);
|
||||||
|
@ -511,13 +520,13 @@ class MatterbridgeManager {
|
||||||
*
|
*
|
||||||
* @param Room $room the room
|
* @param Room $room the room
|
||||||
* @param array $bridge bridge information
|
* @param array $bridge bridge information
|
||||||
* @param $relaunch whether to launch the process if it's down but bridge is enabled
|
* @param bool $relaunch whether to launch the process if it's down but bridge is enabled
|
||||||
* @return int the corresponding matterbridge process ID, 0 if none
|
* @return int the corresponding matterbridge process ID, 0 if none
|
||||||
*/
|
*/
|
||||||
private function checkBridgeProcess(Room $room, array $bridge, bool $relaunch = true): int {
|
private function checkBridgeProcess(Room $room, array $bridge, bool $relaunch = true): int {
|
||||||
$pid = 0;
|
$pid = 0;
|
||||||
|
|
||||||
if (isset($bridge['pid']) && intval($bridge['pid']) !== 0) {
|
if (isset($bridge['pid']) && (int) $bridge['pid'] !== 0) {
|
||||||
// config : there is a PID stored
|
// config : there is a PID stored
|
||||||
$isRunning = $this->isRunning($bridge['pid']);
|
$isRunning = $this->isRunning($bridge['pid']);
|
||||||
// if bridge running and enabled is false : kill it
|
// if bridge running and enabled is false : kill it
|
||||||
|
@ -571,11 +580,11 @@ class MatterbridgeManager {
|
||||||
private function notify(Room $room, string $userId, array $currentBridge, array $newBridge): void {
|
private function notify(Room $room, string $userId, array $currentBridge, array $newBridge): void {
|
||||||
$currentParts = $currentBridge['parts'];
|
$currentParts = $currentBridge['parts'];
|
||||||
$newParts = $newBridge['parts'];
|
$newParts = $newBridge['parts'];
|
||||||
if (count($currentParts) === 0 && count($newParts) > 0) {
|
if (empty($currentParts) && !empty($newParts)) {
|
||||||
$this->sendSystemMessage($room, $userId, 'matterbridge_config_added');
|
$this->sendSystemMessage($room, $userId, 'matterbridge_config_added');
|
||||||
} elseif (count($currentParts) > 0 && count($newParts) === 0) {
|
} elseif (!empty($currentParts) && empty($newParts)) {
|
||||||
$this->sendSystemMessage($room, $userId, 'matterbridge_config_removed');
|
$this->sendSystemMessage($room, $userId, 'matterbridge_config_removed');
|
||||||
} elseif (count($currentParts) !== count($newParts) || !$this->compareBridges($currentBridge, $newBridge)) {
|
} elseif (empty($currentParts) !== empty($newParts) || !$this->compareBridges($currentBridge, $newBridge)) {
|
||||||
$this->sendSystemMessage($room, $userId, 'matterbridge_config_edited');
|
$this->sendSystemMessage($room, $userId, 'matterbridge_config_edited');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -657,7 +666,7 @@ class MatterbridgeManager {
|
||||||
$outputPath = sprintf('/tmp/bridge-%s.log', $room->getToken());
|
$outputPath = sprintf('/tmp/bridge-%s.log', $room->getToken());
|
||||||
$cmd = sprintf('%s -conf %s', $binaryPath, $configPath);
|
$cmd = sprintf('%s -conf %s', $binaryPath, $configPath);
|
||||||
$pid = exec(sprintf('nice -n19 %s > %s 2>&1 & echo $!', $cmd, $outputPath), $output, $ret);
|
$pid = exec(sprintf('nice -n19 %s > %s 2>&1 & echo $!', $cmd, $outputPath), $output, $ret);
|
||||||
$pid = intval($pid);
|
$pid = (int) $pid;
|
||||||
if ($ret !== 0) {
|
if ($ret !== 0) {
|
||||||
$pid = 0;
|
$pid = 0;
|
||||||
}
|
}
|
||||||
|
@ -666,28 +675,47 @@ class MatterbridgeManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* kill the mattermost processes (owned by web server unix user) that do not match with any room
|
* kill the mattermost processes (owned by web server unix user) that do not match with any room
|
||||||
|
* @param bool $killAll
|
||||||
*/
|
*/
|
||||||
public function killZombieBridges(): void {
|
public function killZombieBridges(bool $killAll = false): void {
|
||||||
// get list of running matterbridge processes
|
// get list of running matterbridge processes
|
||||||
$cmd = 'ps -ux | grep "commands/matterbridge" | grep -v grep | awk \'{print $2}\'';
|
$cmd = 'ps -ux | grep "commands/matterbridge" | grep -v grep | awk \'{print $2}\'';
|
||||||
exec($cmd, $output, $ret);
|
exec($cmd, $output, $ret);
|
||||||
$runningPidList = [];
|
$runningPidList = [];
|
||||||
foreach ($output as $o) {
|
foreach ($output as $o) {
|
||||||
array_push($runningPidList, intval($o));
|
$runningPidList[] = (int) $o;
|
||||||
}
|
}
|
||||||
// get list of what should be running
|
|
||||||
$expectedPidList = [];
|
if (empty($runningPidList)) {
|
||||||
$this->manager->forAllRooms(function ($room) use (&$expectedPidList) {
|
// No processes running, so also no zombies
|
||||||
$bridge = $this->getBridgeOfRoom($room);
|
return;
|
||||||
if ($bridge['enabled'] && $bridge['pid'] !== 0) {
|
}
|
||||||
array_push($expectedPidList, intval($bridge['pid']));
|
|
||||||
}
|
if ($killAll) {
|
||||||
});
|
foreach ($runningPidList as $runningPid) {
|
||||||
// kill what should not be running
|
|
||||||
foreach ($runningPidList as $runningPid) {
|
|
||||||
if (!in_array($runningPid, $expectedPidList)) {
|
|
||||||
$this->killPid($runningPid);
|
$this->killPid($runningPid);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get list of what should be running
|
||||||
|
$expectedPidList = [];
|
||||||
|
$query = $this->db->getQueryBuilder();
|
||||||
|
$query->select('*')
|
||||||
|
->from('talk_bridges')
|
||||||
|
->where($query->expr()->eq('enabled', $query->createNamedParameter(1, IQueryBuilder::PARAM_INT)))
|
||||||
|
->andWhere($query->expr()->gt('pid', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||||
|
|
||||||
|
$result = $query->execute();
|
||||||
|
while ($row = $result->fetch()) {
|
||||||
|
$expectedPidList[] = (int) $row['pid'];
|
||||||
|
}
|
||||||
|
$result->closeCursor();
|
||||||
|
|
||||||
|
// kill what should not be running
|
||||||
|
$toKill = array_diff($runningPidList, $expectedPidList);
|
||||||
|
foreach ($toKill as $toKillPid) {
|
||||||
|
$this->killPid($toKillPid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -702,7 +730,7 @@ class MatterbridgeManager {
|
||||||
exec(sprintf('kill -9 %d', $pid), $output, $ret);
|
exec(sprintf('kill -9 %d', $pid), $output, $ret);
|
||||||
// check the process is gone
|
// check the process is gone
|
||||||
$isStillRunning = $this->isRunning($pid);
|
$isStillRunning = $this->isRunning($pid);
|
||||||
return (intval($ret) === 0 && !$isStillRunning);
|
return (int) $ret === 0 && !$isStillRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -714,10 +742,10 @@ class MatterbridgeManager {
|
||||||
private function isRunning(int $pid): bool {
|
private function isRunning(int $pid): bool {
|
||||||
try {
|
try {
|
||||||
$result = shell_exec(sprintf('ps %d', $pid));
|
$result = shell_exec(sprintf('ps %d', $pid));
|
||||||
if (count(preg_split('/\n/', $result)) > 2) {
|
if (count(explode("\n", $result)) > 2) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (\Exception $e) {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -728,19 +756,15 @@ class MatterbridgeManager {
|
||||||
* @return bool success
|
* @return bool success
|
||||||
*/
|
*/
|
||||||
public function stopAllBridges(): bool {
|
public function stopAllBridges(): bool {
|
||||||
$this->manager->forAllRooms(function ($room) {
|
$query = $this->db->getQueryBuilder();
|
||||||
if ($room->getType() === Room::GROUP_CALL || $room->getType() === Room::PUBLIC_CALL) {
|
|
||||||
$bridge = $this->getBridgeOfRoom($room);
|
$query->update('talk_bridges')
|
||||||
// disable bridge in stored config
|
->set('enabled', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
|
||||||
$bridge['enabled'] = false;
|
->set('pid', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT));
|
||||||
$this->saveBridgeToDb($room, $bridge);
|
$query->execute();
|
||||||
// this will kill the bridge process
|
|
||||||
$this->checkBridgeProcess($token, $currentBridge);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// finally kill all potential zombie matterbridge processes
|
// finally kill all potential zombie matterbridge processes
|
||||||
$this->killZombieBridges();
|
$this->killZombieBridges(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -754,31 +778,39 @@ class MatterbridgeManager {
|
||||||
$roomId = $room->getId();
|
$roomId = $room->getId();
|
||||||
|
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
$qb->select('json_values')
|
$qb->select('json_values', 'enabled', 'pid')
|
||||||
->from('talk_bridges', 'b')
|
->from('talk_bridges')
|
||||||
->where(
|
->where(
|
||||||
$qb->expr()->eq('room_id', $qb->createNamedParameter($roomId, IQueryBuilder::PARAM_INT))
|
$qb->expr()->eq('room_id', $qb->createNamedParameter($roomId, IQueryBuilder::PARAM_INT))
|
||||||
);
|
)
|
||||||
$req = $qb->execute();
|
->setMaxResults(1);
|
||||||
$jsonValues = '{"enabled":false,"pid":0,"parts":[]}';
|
$result = $qb->execute();
|
||||||
while ($row = $req->fetch()) {
|
$enabled = false;
|
||||||
|
$pid = 0;
|
||||||
|
$jsonValues = '[]';
|
||||||
|
if ($row = $result->fetch()) {
|
||||||
|
$pid = (int) $row['pid'];
|
||||||
|
$enabled = ((int) $row['enabled'] === 1);
|
||||||
$jsonValues = $row['json_values'];
|
$jsonValues = $row['json_values'];
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
$req->closeCursor();
|
$result->closeCursor();
|
||||||
|
|
||||||
return json_decode($jsonValues, true);
|
return [
|
||||||
|
'enabled' => $enabled,
|
||||||
|
'pid' => $pid,
|
||||||
|
'parts' => json_decode($jsonValues, true),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save bridge information for one room
|
* Save bridge information for one room
|
||||||
*
|
*
|
||||||
* @param Room $room the room
|
* @param int $roomId the room ID
|
||||||
* @param array $bridge bridge values
|
* @param array $bridge bridge values
|
||||||
*/
|
*/
|
||||||
private function saveBridgeToDb(Room $room, array $bridge): void {
|
private function saveBridgeToDb(int $roomId, array $bridge): void {
|
||||||
$roomId = $room->getId();
|
$jsonValues = json_encode($bridge['parts']);
|
||||||
$jsonValues = json_encode($bridge);
|
$intEnabled = $bridge['enabled'] ? 1 : 0;
|
||||||
|
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
try {
|
try {
|
||||||
|
@ -786,16 +818,20 @@ class MatterbridgeManager {
|
||||||
->values([
|
->values([
|
||||||
'room_id' => $qb->createNamedParameter($roomId, IQueryBuilder::PARAM_INT),
|
'room_id' => $qb->createNamedParameter($roomId, IQueryBuilder::PARAM_INT),
|
||||||
'json_values' => $qb->createNamedParameter($jsonValues, IQueryBuilder::PARAM_STR),
|
'json_values' => $qb->createNamedParameter($jsonValues, IQueryBuilder::PARAM_STR),
|
||||||
|
'enabled' => $qb->createNamedParameter($intEnabled, IQueryBuilder::PARAM_INT),
|
||||||
|
'pid' => $qb->createNamedParameter($bridge['pid'], IQueryBuilder::PARAM_INT),
|
||||||
]);
|
]);
|
||||||
$req = $qb->execute();
|
$qb->execute();
|
||||||
} catch (UniqueConstraintViolationException $e) {
|
} catch (UniqueConstraintViolationException $e) {
|
||||||
$qb = $this->db->getQueryBuilder();
|
$qb = $this->db->getQueryBuilder();
|
||||||
$qb->update('talk_bridges');
|
$qb->update('talk_bridges');
|
||||||
$qb->set('json_values', $qb->createNamedParameter($jsonValues, IQueryBuilder::PARAM_STR));
|
$qb->set('json_values', $qb->createNamedParameter($jsonValues, IQueryBuilder::PARAM_STR));
|
||||||
|
$qb->set('enabled', $qb->createNamedParameter($intEnabled, IQueryBuilder::PARAM_INT));
|
||||||
|
$qb->set('pid', $qb->createNamedParameter($bridge['pid'], IQueryBuilder::PARAM_INT));
|
||||||
$qb->where(
|
$qb->where(
|
||||||
$qb->expr()->eq('room_id', $qb->createNamedParameter($roomId, IQueryBuilder::PARAM_INT))
|
$qb->expr()->eq('room_id', $qb->createNamedParameter($roomId, IQueryBuilder::PARAM_INT))
|
||||||
);
|
);
|
||||||
$req = $qb->execute();
|
$qb->execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020, Julien Veyssier <eneiluj@posteo.net>
|
||||||
|
*
|
||||||
|
* @author Julien Veyssier <eneiluj@posteo.net>
|
||||||
|
*
|
||||||
|
* @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\Talk\Migration;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Doctrine\DBAL\Types\Type;
|
||||||
|
use OCP\DB\ISchemaWrapper;
|
||||||
|
use OCP\Migration\IOutput;
|
||||||
|
use OCP\Migration\SimpleMigrationStep;
|
||||||
|
use OCP\IDBConnection;
|
||||||
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
|
|
||||||
|
class Version11000Date20200922161218 extends SimpleMigrationStep {
|
||||||
|
|
||||||
|
/** @var IDBConnection */
|
||||||
|
protected $connection;
|
||||||
|
|
||||||
|
public function __construct(IDBConnection $connection) {
|
||||||
|
$this->connection = $connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param IOutput $output
|
||||||
|
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||||
|
* @param array $options
|
||||||
|
* @return null|ISchemaWrapper
|
||||||
|
*/
|
||||||
|
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||||
|
/** @var ISchemaWrapper $schema */
|
||||||
|
$schema = $schemaClosure();
|
||||||
|
|
||||||
|
if ($schema->hasTable('talk_bridges')) {
|
||||||
|
$table = $schema->getTable('talk_bridges');
|
||||||
|
if (!$table->hasColumn('enabled')) {
|
||||||
|
$table->addColumn('enabled', Type::SMALLINT, [
|
||||||
|
'notnull' => true,
|
||||||
|
'default' => 0,
|
||||||
|
'unsigned' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (!$table->hasColumn('pid')) {
|
||||||
|
$table->addColumn('pid', Type::INTEGER, [
|
||||||
|
'notnull' => true,
|
||||||
|
'default' => 0,
|
||||||
|
'unsigned' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param IOutput $output
|
||||||
|
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||||
|
* @param array $options
|
||||||
|
*/
|
||||||
|
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
|
||||||
|
$bridges = [];
|
||||||
|
$query->select('id', 'json_values')
|
||||||
|
->from('talk_bridges');
|
||||||
|
$result = $query->execute();
|
||||||
|
while ($row = $result->fetch()) {
|
||||||
|
$bridges[] = [
|
||||||
|
'id' => $row['id'],
|
||||||
|
'json_values' => $row['json_values'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$result->closeCursor();
|
||||||
|
|
||||||
|
if (empty($bridges)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->update('talk_bridges')
|
||||||
|
->set('enabled', $query->createParameter('enabled'))
|
||||||
|
->set('pid', $query->createParameter('pid'))
|
||||||
|
->set('json_values', $query->createParameter('json_values'))
|
||||||
|
->where($query->expr()->eq('id', $query->createParameter('id')));
|
||||||
|
|
||||||
|
foreach ($bridges as $bridge) {
|
||||||
|
$values = json_decode($bridge['json_values'], true);
|
||||||
|
if (isset($values['pid'], $values['enabled'])) {
|
||||||
|
$intEnabled = $values['enabled'] ? 1 : 0;
|
||||||
|
$newValues = $values['parts'] ?: [];
|
||||||
|
$encodedNewValues = json_encode($newValues);
|
||||||
|
|
||||||
|
$query->setParameter('enabled', $intEnabled, IQueryBuilder::PARAM_INT)
|
||||||
|
->setParameter('pid', $values['pid'], IQueryBuilder::PARAM_INT)
|
||||||
|
->setParameter('json_values', $encodedNewValues, IQueryBuilder::PARAM_STR);
|
||||||
|
$query->execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче