Files
sofarr/docs/diagrams/activity-matching.puml
Gronod e97bd3c67b docs: comprehensive architecture documentation with PlantUML diagrams
- docs/ARCHITECTURE.md: full system overview, technology stack, directory
  structure, component architecture, data flow, auth, polling/caching,
  download matching pipeline, API reference, frontend architecture,
  configuration, deployment guide
- docs/diagrams/component.puml: system component diagram
- docs/diagrams/seq-auth.puml: authentication sequence diagram
- docs/diagrams/seq-dashboard.puml: dashboard request sequence diagram
- docs/diagrams/seq-polling.puml: background polling cycle sequence
- docs/diagrams/class-server.puml: server-side class/module diagram
- docs/diagrams/class-data.puml: data model / entity diagram
- docs/diagrams/state-ui.puml: frontend UI state diagram
- docs/diagrams/state-poller.puml: poller state diagram
- docs/diagrams/activity-matching.puml: download matching activity diagram
2026-05-16 00:32:16 +01:00

129 lines
3.9 KiB
Plaintext

@startuml activity-matching
!theme plain
title sofarr — Download Matching Activity Diagram
start
:Read cached data from MemoryCache;
note right
poll:sab-queue, poll:sab-history,
poll:sonarr-queue, poll:sonarr-history,
poll:radarr-queue, poll:radarr-history,
poll:sonarr-tags, poll:radarr-tags,
poll:qbittorrent
end note
:Build **seriesMap** from Sonarr queue records
(seriesId → embedded series object);
:Build **moviesMap** from Radarr queue records
(movieId → embedded movie object);
:Build **sonarrTagMap** (tagId → label)
Build **radarrTagMap** (tagId → label);
:Initialise **userDownloads** = [];
partition "Process SABnzbd Queue Slots" {
while (More queue slots?) is (yes)
:Get slot filename (nzbName);
:nzbNameLower = nzbName.toLowerCase();
if (Title matches Sonarr **queue** record?) then (yes)
:series = seriesMap.get(match.seriesId)\n|| match.series;
if (series exists?) then (yes)
:userTag = extractUserTag(series.tags, sonarrTagMap);
if (showAll OR tagMatchesUser?) then (yes)
:Build download object (type=series)
Add coverArt, status, progress, speed, eta
Add importIssues if any
Add admin fields (paths, arrLink);
:Push to **userDownloads**;
endif
endif
endif
if (Title matches Radarr **queue** record?) then (yes)
:movie = moviesMap.get(match.movieId)\n|| match.movie;
if (movie exists?) then (yes)
:userTag = extractUserTag(movie.tags, radarrTagMap);
if (showAll OR tagMatchesUser?) then (yes)
:Build download object (type=movie)
Add coverArt, status, progress, speed, eta
Add importIssues if any
Add admin fields (paths, arrLink);
:Push to **userDownloads**;
endif
endif
endif
endwhile (no)
}
partition "Process SABnzbd History Slots" {
while (More history slots?) is (yes)
:Get slot name (nzbName);
:nzbNameLower = nzbName.toLowerCase();
if (Title matches Sonarr **history** record?) then (yes)
:series = seriesMap.get(match.seriesId)\n|| match.series;
if (series found?) then (yes)
:Check user tag, build download\n(type=series, with completedAt);
:Push to **userDownloads** if tag matches;
endif
endif
if (Title matches Radarr **history** record?) then (yes)
:movie = moviesMap.get(match.movieId)\n|| match.movie;
if (movie found?) then (yes)
:Check user tag, build download\n(type=movie, with completedAt);
:Push to **userDownloads** if tag matches;
endif
endif
endwhile (no)
}
partition "Process qBittorrent Torrents" {
while (More torrents?) is (yes)
:Get torrent name;
:torrentNameLower = name.toLowerCase();
if (Matches Sonarr **queue**?) then (yes)
:Resolve series → check tag;
:mapTorrentToDownload() + enrich;
:Push if matches → **continue**;
elseif (Matches Radarr **queue**?) then (yes)
:Resolve movie → check tag;
:mapTorrentToDownload() + enrich;
:Push if matches → **continue**;
elseif (Matches Sonarr **history**?) then (yes)
:Resolve series via seriesMap;
:mapTorrentToDownload() + enrich;
:Push if matches → **continue**;
elseif (Matches Radarr **history**?) then (yes)
:Resolve movie via moviesMap;
:mapTorrentToDownload() + enrich;
:Push if matches → **continue**;
else (no match)
:Skip torrent (unmatched);
endif
endwhile (no)
}
:Return JSON response
{ user, isAdmin, downloads: userDownloads };
stop
legend right
**Title Matching Logic**
(bidirectional substring, case-insensitive):
""rTitle.includes(dlTitle) || dlTitle.includes(rTitle)""
**Tag Matching Logic**:
1. Exact: tag.toLowerCase() === username
2. Sanitised: sanitizeTagLabel(tag) === sanitizeTagLabel(username)
(handles Ombi-mangled email-style usernames)
end legend
@enduml