- 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
129 lines
3.9 KiB
Plaintext
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
|