# 020 — Architecture

## 1. Components

```
plugin process (short-lived)                    service process (Kodi lifetime)
──────────────────────────────                  ─────────────────────────────────
wcs/library2/urls.py      canonical play URLs   wcs/library2/tracker.py
wcs/library2/resolver.py  action=mplay:         │  xbmc.Player subclass + main-loop queue
  pick source (dialog or auto) → setResolved    │  reads now-playing contract
  writes now-playing contract (Window 10000)    │  saves position every 10 s, on pause/stop/end
wcs/library2/listings.py  My movies/series,     │  applies thresholds → store
  seasons, episodes (Videos window)             │  next episode → countdown → playlist (050)
wcs/library2/rows.py      widget rows (070)     │  mirror → Kodi (Files.SetFileDetails)
wcs/library2/menu.py      context menu (080)    wcs/library2/refresher.py
                                                   TMDb season refresh, new-episode detection
                 ╲                    ╱
                  wcs/library2/store.py  ── SQLite addon_data/.../library.db (030)
                  wcs/library2/meta.py   ── TMDb metadata cache in the same DB (030 §3)
```

`wcs/library2/` is new; old modules stay for MyTV, MyCinema, the Kodi library
path and other skins. Names are fixed by this plan.

## 2. Processes and threads

- Plugin calls (listings, rows, resolver, context actions) open the store,
  do one short transaction, exit.
- The service runs `tracker.run()` on its **main loop**: a `xbmc.Monitor`
  loop (`waitForAbort(0.5)`) that drains a queue filled by the `xbmc.Player`
  callbacks. Player and playlist calls (`seekTime`, `PlayList.add`,
  `playnext`) only happen in that loop (GUI thread-safety rule).
- The countdown dialog is shown by the service loop (as today's managers do,
  modal `doModal` in the loop is acceptable because the tracker pauses while it
  is open; the queue keeps callbacks).

## 3. Contracts

| Contract | Writer → reader | Content |
|----------|-----------------|---------|
| Canonical play URL | listings/rows → Kodi → resolver | `plugin://plugin.video.milionar/?action=mplay&type=movie&tmdb=<id>` · `…&type=episode&tmdb=<show>&s=<n>&e=<n>`; fixed parameter order, no other parameters (040 §2) |
| Play request (options) | `RunPlugin(…action=mplay_req&…)` → resolver | `Window(10000).Property(milionar.play_req)` JSON `{canonical, src: dialog\|auto\|last, from: resume\|start, ts}`; the action then calls `PlayMedia(<canonical>,resume\|noresume)`, so Kodi always plays the canonical URL; the resolver consumes the request only if `canonical` matches and `ts` is < 15 s old (040 §2) |
| Now-playing | resolver → tracker | `Window(10000).Property(milionar.np)` JSON `{token, type, tmdb, s, e, title, show_title, stream_url, ident, provider, start_at, origin}`; tracker matches `stream_url` with `Player.getPlayingFile()` |
| Store change | any writer → skin | bump `milionar.widgets.token` (existing) + `Container.Refresh` when a Videos window shows our listing |
| MyTV / MyCinema | — | their playlist URLs never set `milionar.np`; the tracker ignores any playback without a matching contract |
