Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: modules as plugins #1002 #3062

Draft
wants to merge 45 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
3c15bb1
chore: refactor InstanceModules to not extend CoreBase
Julusian Sep 24, 2024
4b0e317
wip: use global refresh progress store
Julusian Oct 22, 2024
f1773a5
wip: remove concept of builtin modules
Julusian Oct 23, 2024
3c73fa1
Revert "wip: remove concept of builtin modules"
Julusian Nov 4, 2024
2f84b9f
wip: use crude api for module store
Julusian Nov 4, 2024
53ee657
wip: shared module store context
Julusian Nov 4, 2024
8f0b80f
wip: update wording of add connection panel
Julusian Nov 4, 2024
1b48dde
wip
Julusian Nov 4, 2024
daf1f09
wip: refine add connections list
Julusian Nov 5, 2024
d9c9133
wip
Julusian Nov 5, 2024
7e1064d
wip
Julusian Nov 6, 2024
ab04e92
wip: multipart upload
Julusian Nov 7, 2024
5ce5226
wip: upload timeout
Julusian Nov 7, 2024
f4e3844
wip: report progress
Julusian Nov 7, 2024
162ca46
wip: perform import of bundle
Julusian Nov 8, 2024
b85f76b
wip: tidy
Julusian Nov 8, 2024
bc9478e
wip
Julusian Nov 8, 2024
478b597
wip:
Julusian Nov 11, 2024
4dd05be
wip: version ui
Julusian Nov 11, 2024
c22811d
wip: granular reloading
Julusian Nov 11, 2024
41b7349
wip: reorder tabs
Julusian Nov 13, 2024
09a3142
wip: remove concept of builtin modules
Julusian Nov 13, 2024
3484faf
wip: remove bundled-modules from builds, and old tooling
Julusian Nov 13, 2024
549c263
wip: module manage tweaks
Julusian Nov 13, 2024
75dbf5c
wip: crude autoinstall of versions
Julusian Nov 13, 2024
865e14f
wip: rework module manage page
Julusian Nov 13, 2024
9f9748c
fix: allow editing connection label and version while module is missing
Julusian Nov 18, 2024
7764ac8
css tweaks
Julusian Nov 18, 2024
67508f9
wip: connection list
Julusian Nov 18, 2024
f83acc1
fix
Julusian Nov 18, 2024
ef1ff23
change icon
Julusian Nov 18, 2024
18d1a67
tidying
Julusian Nov 18, 2024
aff8ed2
indicate deprecated modules
Julusian Nov 18, 2024
93cd1af
wip: one click install all missing versions
Julusian Nov 18, 2024
1375bea
indicate new version is available in list, and update policy
Julusian Nov 18, 2024
961185c
fix: import preserve module versions
Julusian Nov 18, 2024
9b5746c
fix: import broken modules
Julusian Nov 18, 2024
979fbfe
lint issues
Julusian Nov 18, 2024
5d84cce
Merge branch 'main' into feat/loadable-modules2
Julusian Nov 19, 2024
18c8b2e
fix package.json and yarn.lock
Julusian Nov 24, 2024
bbc317e
wip: move isPrerelease to property in manifest
Julusian Nov 24, 2024
9aad31b
wip: rename prerelease to beta
Julusian Nov 24, 2024
794c544
wip: tidy
Julusian Nov 24, 2024
58246f1
wip: tidying
Julusian Nov 24, 2024
1ae05d9
wip: simplify types. move legacy check to a file inside modules
Julusian Nov 24, 2024
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
51 changes: 0 additions & 51 deletions .github/workflows/sync-modules.yml

This file was deleted.

4 changes: 2 additions & 2 deletions companion/lib/Data/StoreBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,8 @@ export abstract class DataStoreBase {
* @param table - the table to get from
* @returns the rows
*/
public getTable(table: string): any {
let out = {}
public getTable(table: string): Record<string, any> {
let out: Record<string, any> = {}

if (table.length > 0) {
const query = this.store.prepare(`SELECT id, value FROM ${table}`)
Expand Down
3 changes: 3 additions & 0 deletions companion/lib/ImportExport/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,7 @@ export class ImportExportController {

clientObject.instances[instanceId] = {
instance_type: this.#instancesController.modules.verifyInstanceTypeIsCurrent(instance.instance_type),
moduleVersionId: instance.moduleVersionId ?? null,
label: instance.label,
sortOrder: instance.sortOrder,
}
Expand Down Expand Up @@ -992,6 +993,8 @@ export class ImportExportController {
const [newId, newConfig] = this.#instancesController.addInstanceWithLabel(
{ type: instance_type },
obj.label,
obj.moduleVersionId ?? null,
obj.updatePolicy,
true
)
if (newId && newConfig) {
Expand Down
12 changes: 11 additions & 1 deletion companion/lib/Instance/ConnectionConfigStore.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { ConnectionConfig, ClientConnectionConfig } from '@companion-app/shared/Model/Connections.js'
import {
ConnectionConfig,
ClientConnectionConfig,
ConnectionUpdatePolicy,
} from '@companion-app/shared/Model/Connections.js'
import { DataDatabase } from '../Data/Database.js'
// import LogController from '../Log/Controller.js'
import { nanoid } from 'nanoid'
Expand Down Expand Up @@ -54,6 +58,8 @@ export class ConnectionConfigStore {
moduleType: string,
label: string,
product: string | undefined,
moduleVersionId: string | null,
updatePolicy: ConnectionUpdatePolicy,
disabled: boolean
): [id: string, config: ConnectionConfig] {
// Find the highest rank given to an instance
Expand All @@ -69,6 +75,8 @@ export class ConnectionConfigStore {

this.#store[id] = {
instance_type: moduleType,
moduleVersionId: moduleVersionId,
updatePolicy: updatePolicy,
sortOrder: highestRank + 1,
label: label,
isFirstInit: true,
Expand Down Expand Up @@ -101,6 +109,8 @@ export class ConnectionConfigStore {

result[id] = {
instance_type: config.instance_type,
moduleVersionId: config.moduleVersionId,
updatePolicy: config.updatePolicy,
label: config.label,
enabled: config.enabled,
sortOrder: config.sortOrder,
Expand Down
132 changes: 106 additions & 26 deletions companion/lib/Instance/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ import { InstanceModules } from './Modules.js'
import type { ControlsController } from '../Controls/Controller.js'
import type { VariablesController } from '../Variables/Controller.js'
import type { ConnectionStatusEntry } from '@companion-app/shared/Model/Common.js'
import type {
import {
ClientConnectionConfig,
ClientConnectionsUpdate,
ConnectionConfig,
ConnectionUpdatePolicy,
} from '@companion-app/shared/Model/Connections.js'
import type { ModuleManifest } from '@companion-module/base'
import type { ExportInstanceFullv4, ExportInstanceMinimalv4 } from '@companion-app/shared/Model/ExportModel.js'
Expand All @@ -41,6 +42,10 @@ import type { DataDatabase } from '../Data/Database.js'
import type { GraphicsController } from '../Graphics/Controller.js'
import type { PageController } from '../Page/Controller.js'
import express from 'express'
import { InstanceInstalledModulesManager } from './InstalledModulesManager.js'
import { ModuleStoreService } from './ModuleStore.js'
import type { AppInfo } from '../Registry.js'
import type { DataCache } from '../Data/Cache.js'

const InstancesRoom = 'instances'

Expand Down Expand Up @@ -71,12 +76,16 @@ export class InstanceController extends EventEmitter<InstanceControllerEvents> {
readonly moduleHost: ModuleHost
readonly modules: InstanceModules
readonly sharedUdpManager: InstanceSharedUdpManager
readonly modulesStore: ModuleStoreService
readonly userModulesManager: InstanceInstalledModulesManager

readonly connectionApiRouter = express.Router()

constructor(
appInfo: AppInfo,
io: UIHandler,
db: DataDatabase,
cache: DataCache,
apiRouter: express.Router,
controls: ControlsController,
graphics: GraphicsController,
Expand All @@ -95,7 +104,7 @@ export class InstanceController extends EventEmitter<InstanceControllerEvents> {
this.sharedUdpManager = new InstanceSharedUdpManager()
this.definitions = new InstanceDefinitions(io, controls, graphics, variables.values)
this.status = new InstanceStatus(io, controls)
this.modules = new InstanceModules(io, this, apiRouter)
this.modules = new InstanceModules(io, this, apiRouter, appInfo.modulesDir)
this.moduleHost = new ModuleHost(
{
controls: controls,
Expand All @@ -114,6 +123,20 @@ export class InstanceController extends EventEmitter<InstanceControllerEvents> {
this.modules,
this.#configStore
)
this.modulesStore = new ModuleStoreService(io, cache)
this.userModulesManager = new InstanceInstalledModulesManager(
appInfo,
db,
io,
this.modules,
this.modulesStore,
this.#configStore,
appInfo.modulesDir,
(connectionId) => {
this.enableDisableInstance(connectionId, false)
this.enableDisableInstance(connectionId, true)
}
)

graphics.on('resubscribeFeedbacks', () => this.moduleHost.resubscribeAllFeedbacks())

Expand Down Expand Up @@ -160,22 +183,27 @@ export class InstanceController extends EventEmitter<InstanceControllerEvents> {
* @param extraModulePath - extra directory to search for modules
*/
async initInstances(extraModulePath: string): Promise<void> {
const connectionIds = this.#configStore.getAllInstanceIds()
this.#logger.silly('instance_init', connectionIds)
await this.userModulesManager.init()

await this.modules.initInstances(extraModulePath)

const connectionIds = this.#configStore.getAllInstanceIds()
this.#logger.silly('instance_init', connectionIds)
for (const id of connectionIds) {
this.#activate_module(id, false)
}

this.emit('connection_added')
}

reloadUsesOfModule(moduleId: string): void {
async reloadUsesOfModule(moduleId: string, versionId: string): Promise<void> {
// restart usages of this module
const { connectionIds, labels } = this.#configStore.findActiveUsagesOfModule(moduleId)
for (const id of connectionIds) {
// Skip any that we know are not using this version
const config = this.#configStore.getConfigForId(id)
if (config && config.moduleVersionId !== versionId) continue

// Restart it
this.enableDisableInstance(id, false)
this.enableDisableInstance(id, true)
Expand Down Expand Up @@ -236,36 +264,34 @@ export class InstanceController extends EventEmitter<InstanceControllerEvents> {
this.#logger.debug(`instance "${connectionConfig.label}" configuration updated`)
}

/**
* Add a new instance of a module
*/
addInstance(data: CreateConnectionData, disabled: boolean): string {
let module = data.type

const moduleInfo = this.modules.getModuleManifest(module)
if (!moduleInfo) throw new Error(`Unknown module type ${module}`)

return this.addInstanceWithLabel(data, moduleInfo.display.shortname, disabled)[0]
}

/**
* Add a new instance of a module with a predetermined label
*/
addInstanceWithLabel(
data: CreateConnectionData,
labelBase: string,
versionId: string | null,
updatePolicy: ConnectionUpdatePolicy,
disabled: boolean
): [id: string, config: ConnectionConfig] {
let module = data.type
let moduleId = data.type
let product = data.product

if (versionId === null) {
// Get the latest installed version
versionId = this.modules.getLatestVersionOfModule(moduleId)
}

// Ensure the requested module and version is installed
this.userModulesManager.ensureModuleIsInstalled(moduleId, versionId)

const label = this.#configStore.makeLabelUnique(labelBase)

if (this.getIdForLabel(label)) throw new Error(`Label "${label}" already in use`)

this.#logger.info('Adding connection ' + module + ' ' + product)
this.#logger.info('Adding connection ' + moduleId + ' ' + product)

const [id, config] = this.#configStore.addConnection(module, label, product, disabled)
const [id, config] = this.#configStore.addConnection(moduleId, label, product, versionId, updatePolicy, disabled)

this.#activate_module(id, true)

Expand All @@ -289,7 +315,7 @@ export class InstanceController extends EventEmitter<InstanceControllerEvents> {
const config = this.#configStore.getConfigForId(id)
if (!config) return undefined

const moduleManifest = this.modules.getModuleManifest(config.instance_type)
const moduleManifest = this.modules.getModuleManifest(config.instance_type, config.moduleVersionId)

return moduleManifest?.manifest
}
Expand Down Expand Up @@ -467,6 +493,11 @@ export class InstanceController extends EventEmitter<InstanceControllerEvents> {

config.instance_type = this.modules.verifyInstanceTypeIsCurrent(config.instance_type)

// Seamless fixup old configs
if (config.moduleVersionId === undefined) {
config.moduleVersionId = this.modules.getLatestVersionOfModule(config.instance_type)
}

if (config.enabled === false) {
this.#logger.silly("Won't load disabled module " + id + ' (' + config.instance_type + ')')
this.status.updateInstanceStatus(id, null, 'Disabled')
Expand All @@ -485,9 +516,14 @@ export class InstanceController extends EventEmitter<InstanceControllerEvents> {
// TODO this could check if anything above changed, or is_being_created
this.#configStore.commitChanges([id])

const moduleInfo = this.modules.getModuleManifest(config.instance_type)
const moduleInfo = this.modules.getModuleManifest(config.instance_type, config.moduleVersionId)
if (!moduleInfo) {
this.#logger.error('Configured instance ' + config.instance_type + ' could not be loaded, unknown module')
if (this.modules.hasModule(config.instance_type)) {
this.status.updateInstanceStatus(id, 'system', 'Unknown module version')
} else {
this.status.updateInstanceStatus(id, 'system', 'Unknown module')
}
} else {
this.moduleHost.queueRestartConnection(id, config, moduleInfo).catch((e) => {
this.#logger.error('Configured instance ' + config.instance_type + ' failed to start: ', e)
Expand All @@ -503,6 +539,8 @@ export class InstanceController extends EventEmitter<InstanceControllerEvents> {
this.definitions.clientConnect(client)
this.status.clientConnect(client)
this.modules.clientConnect(client)
this.modulesStore.clientConnect(client)
this.userModulesManager.clientConnect(client)

client.onPromise('connections:subscribe', () => {
client.join(InstancesRoom)
Expand Down Expand Up @@ -535,7 +573,7 @@ export class InstanceController extends EventEmitter<InstanceControllerEvents> {
}
})

client.onPromise('connections:set-config', (id, label, config) => {
client.onPromise('connections:set-label-and-config', (id, label, config) => {
const idUsingLabel = this.getIdForLabel(label)
if (idUsingLabel && idUsingLabel !== id) {
return 'duplicate label'
Expand All @@ -550,6 +588,48 @@ export class InstanceController extends EventEmitter<InstanceControllerEvents> {
return null
})

client.onPromise('connections:set-label-and-version', (id, label, versionId, updatePolicy) => {
this.#logger.info('Setting label and version', id, label, versionId)
const idUsingLabel = this.getIdForLabel(label)
if (idUsingLabel && idUsingLabel !== id) {
return 'duplicate label'
}

if (!isLabelValid(label)) {
return 'invalid label'
}

// TODO - refactor/optimise/tidy this

this.setInstanceLabelAndConfig(id, label, null)

const config = this.#configStore.getConfigForId(id)
if (!config) return 'no connection'

// Don't validate the version, as it might not yet be installed
// const moduleInfo = this.modules.getModuleManifest(config.instance_type, versionId)
// if (!moduleInfo) throw new Error(`Unknown module type or version ${config.instance_type} (${versionId})`)

// Update the config
config.moduleVersionId = versionId
if (updatePolicy) config.updatePolicy = updatePolicy
this.#configStore.commitChanges([id])

// Install the module if needed
const moduleInfo = this.modules.getModuleManifest(config.instance_type, versionId)
if (!moduleInfo) {
this.userModulesManager.ensureModuleIsInstalled(config.instance_type, versionId)
}

// Trigger a restart (or as much as possible)
if (config.enabled) {
this.enableDisableInstance(id, false)
this.enableDisableInstance(id, true)
}

return null
})

client.onPromise('connections:set-enabled', (id, state) => {
this.enableDisableInstance(id, !!state)
})
Expand All @@ -558,9 +638,9 @@ export class InstanceController extends EventEmitter<InstanceControllerEvents> {
await this.deleteInstance(id)
})

client.onPromise('connections:add', (module) => {
const id = this.addInstance(module, false)
return id
client.onPromise('connections:add', (module, label, version) => {
const connectionInfo = this.addInstanceWithLabel(module, label, version, ConnectionUpdatePolicy.Stable, false)
return connectionInfo[0]
})

client.onPromise('connections:set-order', async (connectionIds) => {
Expand Down
Loading
Loading