Plugins Guide - nicho92/MtgDesktopCompanion GitHub Wiki

MTGPlugin Guide — Building a MTGCompanion Plugin

This guide explains the full plugin architecture in MTGCompanion and how to choose the right interface/abstract class based on your use case.


1) Plugin architecture overview

The core contract is MTGPlugin. Each plugin family (provider, pricer, dao, notifier, server, etc.) extends MTGPlugin through a dedicated business interface (MTGCardsProvider, MTGDao, MTGPricesProvider, ...).

In practice, a concrete plugin class:

  1. Implements a business interface (usually through an abstract base class),
  2. Is instantiated by PluginRegistry,
  3. Is enabled/disabled from XML config,
  4. Reads/writes its own plugin-specific .conf file.

2) Base contract: MTGPlugin

MTGPlugin defines common behavior:

  • lifecycle (load, save, unload, isLoaded),
  • enablement (enable, isEnable),
  • metadata (getName, getVersion, getType, getStatut),
  • config (getDefaultAttributes, setProperty, getString, getConfFile),
  • observer support (addObserver, removeObserver, listObservers),
  • docs/auth (getDocumentation, listAuthenticationAttributes, needAuthenticator).

It also defines structural enums:

  • PLUGINS: category (PROVIDER, DAO, PRICER, SERVER, etc.),
  • STATUT: maturity (DEV/BETA/STABLE/DEPRECATED/BUGGED).

Design implication: every plugin must expose a stable identity (getName) and category (getType) to be properly registered and loaded.


3) Common base class: AbstractMTGPlugin

Most implementations should extend AbstractMTGPlugin (directly or indirectly) instead of implementing MTGPlugin from scratch.

It already provides:

  • plugin .conf loading/saving,
  • default attribute initialization (initDefault + getDefaultAttributes),
  • typed config helpers (getInt, getBoolean, getDouble, getFile, ...),
  • icon resolution (/icons/plugins/<name>.png with fallback),
  • default documentation URL convention,
  • partner/auth helpers,
  • observer notification support.

Important details

  • getString(k) auto-creates missing keys from getDefaultAttributes().
  • isEnable() can be forced to true when isPartner() returns true.
  • conf files are organized per plugin type (<conf_dir>/<type>/<name>.conf).

4) Discovery and loading: PluginRegistry

PluginRegistry is the central registry.

It stores mapping metadata: interface ↔ package to scan ↔ plugin type ↔ XML xpath.

Examples (from init()):

  • MTGCardsProviderorg.magic.api.providers.implPLUGINS.PROVIDER/providers/provider,
  • MTGDaoorg.magic.api.dao.implPLUGINS.DAO/daos/dao,
  • MTGPricesProviderorg.magic.api.pricers.implPLUGINS.PRICER/pricers/pricer.

How loading works

  1. Controller injects XML config into registry (setConfig).
  2. listPlugins(interface) reads XML entries (class + enable).
  3. Instances are created by reflection (newInstance).
  4. Enabled/disabled state is applied.
  5. Instances are cached in PluginEntry.

Auto-registration of new modules

updateConfigWithNewModule() scans configured packages with Reflections and adds missing plugin classes into XML (non-abstract classes implementing MTGPlugin).


5) MTGControler responsibilities

MTGControler:

  • initializes PluginRegistry with active config,
  • persists plugin enablement (setProperty(plugin, true/false)),
  • can add missing plugin class entries (addProperty),
  • calls unload() for all plugins on shutdown,
  • initializes critical plugins on startup (MTGCardsProvider + MTGDao).

6) Choosing interface + abstract class by need

Practical rule:

  1. Pick the business interface matching your use case,
  2. Extend the associated abstract base class whenever available.

Common mappings

  • Card data source: MTGCardsProvider + AbstractCardsProvider
  • Price source: MTGPricesProvider + AbstractPricesProvider
  • Database backend: MTGDao + AbstractMagicDAO (or SQL/key-value variants)
  • Notifications: MTGNotifier + AbstractMTGNotifier
  • Deck import: MTGDeckSniffer + AbstractDeckSniffer
  • Deck/collection export: MTGCardsExport + AbstractCardExport
  • Card images: MTGPictureProvider + AbstractPicturesProvider
  • Embedded service/server: MTGServer + AbstractMTGServer (or AbstractWebServer / AbstractWarServer)
  • Market dashboard: MTGDashBoard + AbstractDashBoard
  • Tokens: MTGTokensProvider + AbstractTokensProvider
  • News: MTGNewsProvider + AbstractMagicNewsProvider
  • External store sync: MTGExternalShop + AbstractExternalShop
  • AI integration: MTGIA + AbstractIA
  • Sealed products: MTGSealedProvider + AbstractSealedProvider

7) Example pattern: card provider plugin

ScryFallProvider is a representative plugin implementation:

  • extends AbstractCardsProvider,
  • implements card retrieval/search behavior (searchByCriteria, getCardById, ...),
  • defines defaults with getDefaultAttributes(),
  • initializes caches/background warmup in init().

This same pattern applies to other plugin families: let the abstract base handle plumbing, keep concrete class focused on business logic.


8) Recommended workflow to create a plugin

Step A — choose the plugin family

Define the category first (provider/pricer/dao/notifier/server/etc.).

Step B — place class in the expected package

Use the package path expected by PluginRegistry, otherwise auto-discovery may not pick it up.

Step C — implement identity

Always define:

  • getName() (stable unique name),
  • getType() (often already provided by abstract base),
  • getDefaultAttributes() (keys + default values + descriptions).

Step D — implement required business methods

Implement mandatory methods from your selected interface.

Step E — initialization strategy

Use init() for warmup (cache/index/network setup). Avoid heavy constructor logic.

Step F — authentication support (if needed)

If plugin needs credentials/API key:

  • return attribute names in listAuthenticationAttributes(),
  • needAuthenticator() becomes true automatically when the list is not empty.

Step G — plugin documentation

Override getDocumentation() only if custom behavior is needed; default behavior builds a wiki URL convention.

Step H — activation and validation

  • check class appears in plugin config,
  • enable the plugin,
  • verify generated .conf file,
  • test from the UI/API flows consuming the interface.

9) Design best practices

  • Stable plugin name: avoid changing getName() after release (conf/id impact).
  • Complete defaults: every accessed config key should exist in getDefaultAttributes().
  • Resilient network behavior: log errors and degrade gracefully.
  • Keep constructors light: prefer init() for heavy setup.
  • Explicit maturity: override getStatut() when relevant.
  • Provide icon: add /icons/plugins/<lowercase-name>.png.

10) Quick pre-PR checklist

  • Correct business interface selected.
  • Appropriate abstract base class used.
  • Package matches PluginRegistry discovery rules.
  • getName() is stable and meaningful.
  • getDefaultAttributes() is complete.
  • init() is robust and non-blocking.
  • Auth fields declared when needed.
  • Icon + documentation available.
  • Functional validation done (enable/config/business calls).

11) Fast decision guide

  • Need to retrieve card dataMTGCardsProvider / AbstractCardsProvider
  • Need to retrieve pricesMTGPricesProvider / AbstractPricesProvider
  • Need to persist/read collection dataMTGDao / AbstractMagicDAO
  • Need to send notificationsMTGNotifier / AbstractMTGNotifier
  • Need to run embedded servicesMTGServer / AbstractMTGServer
⚠️ **GitHub.com Fallback** ⚠️