Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,15 @@
'token' => '^[a-z0-9]{4,30}$',
],
],
[
'name' => 'Matterbridge#getBridgeProcessState',
'url' => '/api/{apiVersion}/bridge/{token}/process',
'verb' => 'GET',
'requirements' => [
'apiVersion' => 'v1',
'token' => '^[a-z0-9]{4,30}$',
],
],
[
'name' => 'Matterbridge#editBridgeOfRoom',
'url' => '/api/{apiVersion}/bridge/{token}',
Expand Down
22 changes: 19 additions & 3 deletions lib/Controller/MatterbridgeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,27 @@ public function __construct(string $appName,
* @return DataResponse
*/
public function getBridgeOfRoom(): DataResponse {
$this->bridgeManager->checkBridge($this->room);
$pid = $this->bridgeManager->checkBridge($this->room);
$logContent = $this->bridgeManager->getBridgeLog($this->room);
$bridge = $this->bridgeManager->getBridgeOfRoom($this->room);
$bridge['running'] = ($pid !== 0);
$bridge['log'] = $logContent;
return new DataResponse($bridge);
}

/**
* Get bridge process information
*
* @NoAdminRequired
* @RequireLoggedInModeratorParticipant
*
* @return DataResponse
*/
public function getBridgeProcessState(): DataResponse {
$result = $this->bridgeManager->getBridgeProcessState($this->room);
return new DataResponse($result);
Comment on lines +80 to +81
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$result = $this->bridgeManager->getBridgeProcessState($this->room);
return new DataResponse($result);
$state = $this->bridgeManager->getBridgeProcessState($this->room);
return new DataResponse($state);

}

/**
* Edit bridge information of one room
*
Expand All @@ -77,11 +93,11 @@ public function getBridgeOfRoom(): DataResponse {
*/
public function editBridgeOfRoom(bool $enabled, array $parts = []): DataResponse {
try {
$success = $this->bridgeManager->editBridgeOfRoom($this->room, $enabled, $parts);
$state = $this->bridgeManager->editBridgeOfRoom($this->room, $enabled, $parts);
} catch (ImpossibleToKillException $e) {
return new DataResponse(['error' => $e->getMessage()], Http::STATUS_NOT_ACCEPTABLE);
}
return new DataResponse($success);
return new DataResponse($state);
}

/**
Expand Down
75 changes: 58 additions & 17 deletions lib/MatterbridgeManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,45 @@ public function getBridgeOfRoom(Room $room): array {
return $this->getBridgeFromDb($room);
}

/**
* Get bridge process information for a specific room
*
* @param Room $room the room
* @return array process state and log
*/
public function getBridgeProcessState(Room $room): array {
$bridge = $this->getBridgeFromDb($room);

$logContent = $this->getBridgeLog($room);

$pid = $this->checkBridgeProcess($room, $bridge, false);
return [
'running' => ($pid !== 0),
'log' => $logContent
];
}

/**
* Get bridge log file content
*
* @param Room $room the room
* @return string log file content
*/
public function getBridgeLog(Room $room): string {
$outputPath = sprintf('/tmp/bridge-%s.log', $room->getToken());
$logContent = file_get_contents($outputPath);
return $logContent !== false ? $logContent : '';
}

/**
* Edit bridge information for a room
*
* @param Room $room the room
* @param bool $enabled desired state of the bridge
* @param array $parts parts of the bridge (what it connects to)
* @return bool success
* @return array bridge state
*/
public function editBridgeOfRoom(Room $room, bool $enabled, array $parts = []): bool {
public function editBridgeOfRoom(Room $room, bool $enabled, array $parts = []): array {
$currentBridge = $this->getBridgeOfRoom($room);
$newBridge = [
'enabled' => $enabled,
Expand All @@ -118,7 +148,11 @@ public function editBridgeOfRoom(Room $room, bool $enabled, array $parts = []):
// save config
$this->saveBridgeToDb($room, $newBridge);

return true;
$logContent = $this->getBridgeLog($room);
return [
'running' => ($pid !== 0),
'log' => $logContent
];
}

/**
Expand Down Expand Up @@ -153,15 +187,17 @@ public function checkAllBridges(): void {
/**
* For one room, check mattermost process respects desired state
* @param Room $room the room
* @return int the bridge process ID
*/
public function checkBridge(Room $room): void {
public function checkBridge(Room $room): int {
$bridge = $this->getBridgeOfRoom($room);
$pid = $this->checkBridgeProcess($room, $bridge);
if ($pid !== $bridge['pid']) {
// save the new PID if necessary
$bridge['pid'] = $pid;
$this->saveBridgeToDb($room, $bridge);
}
return $pid;
}

private function getDataFolder(): ISimpleFolder {
Expand Down Expand Up @@ -437,43 +473,48 @@ private function cleanUrl(string $url): string {
*
* @param Room $room the room
* @param array $bridge bridge information
* @param $relaunch whether to launch the process if it's down but bridge is enabled
* @return int the corresponding matterbridge process ID, 0 if none
*/
private function checkBridgeProcess(Room $room, array $bridge): int {
private function checkBridgeProcess(Room $room, array $bridge, bool $relaunch = true): int {
$pid = 0;

if (isset($bridge['pid']) && intval($bridge['pid']) !== 0) {
// config : there is a PID stored
$pid = intval($bridge['pid']);
$isRunning = $this->isRunning($pid);
$isRunning = $this->isRunning($bridge['pid']);
// if bridge running and enabled is false : kill it
if ($isRunning) {
if ($bridge['enabled']) {
$this->logger->info('Process running AND bridge enabled in config : doing nothing');
$pid = $bridge['pid'];
} else {
$this->logger->info('Process running AND bridge disabled in config : KILL ' . $pid);
$killed = $this->killPid($pid);
$this->logger->info('Process running AND bridge disabled in config : KILL ' . $bridge['pid']);
$killed = $this->killPid($bridge['pid']);
if ($killed) {
$pid = 0;
} else {
$this->logger->info('Impossible to kill ' . $pid);
throw new ImpossibleToKillException('Impossible to kill bridge process [' . $pid . ']');
$this->logger->info('Impossible to kill ' . $bridge['pid']);
throw new ImpossibleToKillException('Impossible to kill bridge process [' . $bridge['pid'] . ']');
}
}
} else {
// no process found
if ($bridge['enabled']) {
$this->logger->info('Process not found AND bridge enabled in config : relaunching');
$pid = $this->launchMatterbridge($room);
if ($relaunch) {
$this->logger->info('Process not found AND bridge enabled in config : relaunching');
$pid = $this->launchMatterbridge($room);
}
} else {
$this->logger->info('Process not found AND bridge disabled in config : doing nothing');
}
}
} elseif ($bridge['enabled']) {
// config : no PID stored
// config : enabled => launch it
$pid = $this->launchMatterbridge($room);
$this->logger->info('Launch process, PID is '.$pid);
if ($relaunch) {
// config : no PID stored
// config : enabled => launch it
$pid = $this->launchMatterbridge($room);
$this->logger->info('Launch process, PID is '.$pid);
}
} else {
$this->logger->info('No PID defined in config AND bridge disabled in config : doing nothing');
}
Expand Down
61 changes: 58 additions & 3 deletions src/components/RightSidebar/Matterbridge/MatterbridgeSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,17 @@
:checked="enabled"
@update:checked="onEnabled">
{{ t('spreed', 'Enabled') }}
({{ processStateText }})
</ActionCheckbox>
<button class="" @click="showLogContent">
{{ t('spreed', 'Show matterbridge log') }}
</button>
<Modal v-if="logModal"
@close="closeLogModal">
<div class="modal__content">
<textarea v-model="processLog" class="log-content" />
</div>
</Modal>
<Multiselect
ref="partMultiselect"
v-model="selectedType"
Expand Down Expand Up @@ -73,11 +83,13 @@
import {
editBridge,
getBridge,
getBridgeProcessState,
} from '../../../services/matterbridgeService'
import { showSuccess } from '@nextcloud/dialogs'
import ActionCheckbox from '@nextcloud/vue/dist/Components/ActionCheckbox'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
import Modal from '@nextcloud/vue/dist/Components/Modal'
import BridgePart from './BridgePart'

export default {
Expand All @@ -87,6 +99,7 @@ export default {
ActionButton,
Multiselect,
BridgePart,
Modal,
},

mixins: [
Expand All @@ -100,6 +113,10 @@ export default {
enabled: false,
parts: [],
loading: false,
processRunning: null,
processLog: '',
logModal: false,
stateLoop: null,
types: {
nctalk: {
name: t('spreed', 'Nextcloud Talk'),
Expand Down Expand Up @@ -414,6 +431,7 @@ export default {
token() {
const token = this.$store.getters.getToken()
this.getBridge(token)
this.relaunchStateLoop(token)
return token
},
formatedTypes() {
Expand All @@ -430,6 +448,13 @@ export default {
return p.type !== 'nctalk' || p.channel !== this.token
})
},
processStateText() {
return this.processRunning === null
? t('spreed', 'unknown state')
: this.processRunning
? t('spreed', 'running')
: t('spreed', 'not running')
},
},

beforeMount() {
Expand All @@ -439,6 +464,11 @@ export default {
},

methods: {
relaunchStateLoop(token) {
// start loop to periodically get bridge state
clearInterval(this.stateLoop)
this.stateLoop = setInterval(() => this.getBridgeProcessState(token), 60000)
},
clickAddPart() {
const typeKey = this.selectedType.type
const type = this.types[typeKey]
Expand All @@ -460,17 +490,17 @@ export default {
this.onSave()
},
onSave() {
console.debug(this.parts)
this.editBridge(this.token, this.enabled, this.parts)
},
async getBridge(token) {
this.loading = true
try {
const result = await getBridge(token)
console.debug(result)
const bridge = result.data.ocs.data
this.enabled = bridge.enabled
this.parts = bridge.parts
this.processLog = bridge.log
this.processRunning = bridge.running
} catch (exception) {
console.debug(exception)
}
Expand All @@ -479,13 +509,32 @@ export default {
async editBridge() {
this.loading = true
try {
await editBridge(this.token, this.enabled, this.parts)
const result = await editBridge(this.token, this.enabled, this.parts)
this.processLog = result.data.ocs.data.log
this.processRunning = result.data.ocs.data.running
showSuccess(t('spreed', 'Bridge saved'))
} catch (exception) {
console.debug(exception)
}
this.loading = false
},
async getBridgeProcessState(token) {
try {
const result = await getBridgeProcessState(token)
this.processLog = result.data.ocs.data.log
this.processRunning = result.data.ocs.data.running
console.debug(result.data.ocs.data.log)
} catch (exception) {
console.debug(exception)
}
},
showLogContent() {
this.getBridgeProcessState(this.token)
this.logModal = true
},
closeLogModal() {
this.logModal = false
},
},
}
</script>
Expand Down Expand Up @@ -514,6 +563,7 @@ export default {
}

.basic-settings {
button,
.multiselect {
width: calc(100% - 40px);
margin-left: 40px;
Expand All @@ -523,4 +573,9 @@ export default {
ul {
margin-bottom: 64px;
}

.log-content {
width: 600px;
height: 400px;
}
</style>
10 changes: 10 additions & 0 deletions src/services/matterbridgeService.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ const getBridge = async function(token) {
return response
}

/**
* Get the bridge binary state for a room
* @param {token} token the conversation token.
*/
const getBridgeProcessState = async function(token) {
const response = await axios.get(generateOcsUrl('apps/spreed/api/v1', 2) + `bridge/${token}/process`)
return response
}

/**
* Ask to stop all bridges (and kill all related processes)
*/
Expand All @@ -71,6 +80,7 @@ const getMatterbridgeVersion = async function() {
export {
editBridge,
getBridge,
getBridgeProcessState,
stopAllBridges,
getMatterbridgeVersion,
enableMatterbridgeApp,
Expand Down