seq-auth: - startAutoRefresh() -> startSSE(), stopAutoRefresh() -> stopSSE() - Cookie secure flag: 'secure (prod)' -> 'secure (if TRUST_PROXY)' component: - Fix typo creatApp -> createApp - Add GET /csrf, POST /logout to browser->auth arrow - Add GET /stream (SSE) to browser->dashboard arrow class-server: - Add subscribers Set, onPollComplete(), offPollComplete() to Poller class class-data: - Add SSE Event /stream shape alongside API Response /user-downloads - Add sser *-- dl relationship state-ui: - Fix invalid multi-line transition labels with raw Unicode arrows (broke PlantUML parser); replace with valid \n escapes on single line seq-dashboard, seq-polling, state-poller, activity-matching: - Whitespace touch to trigger render-diagrams CI workflow
158 lines
5.0 KiB
Plaintext
158 lines
5.0 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);
|
|
|
|
if (showAll?) then (yes)
|
|
:Fetch full Emby user list
|
|
Build **embyUserMap** (lowerName → displayName)
|
|
[cached 60s];
|
|
endif
|
|
|
|
: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)
|
|
:allTags = extractAllTags(series.tags, sonarrTagMap)
|
|
matchedUserTag = extractUserTag(series.tags, sonarrTagMap, username);
|
|
if (showAll AND hasAnyTag?) then (yes)
|
|
:Build download object (type=series)
|
|
Add coverArt, status, progress, speed, eta
|
|
Add allTags, matchedUserTag
|
|
Add tagBadges = buildTagBadges(allTags, embyUserMap)
|
|
Add importIssues if any
|
|
Add admin fields (paths, arrLink);
|
|
:Push to **userDownloads**;
|
|
elseif (NOT showAll AND matchedUserTag?) then (yes)
|
|
:Build download object (type=series)
|
|
Add matchedUserTag;
|
|
: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)
|
|
:allTags = extractAllTags(movie.tags, radarrTagMap)
|
|
matchedUserTag = extractUserTag(movie.tags, radarrTagMap, username);
|
|
if (showAll AND hasAnyTag?) then (yes)
|
|
:Build download object (type=movie)
|
|
Add coverArt, status, progress, speed, eta
|
|
Add allTags, matchedUserTag, tagBadges
|
|
Add importIssues if any
|
|
Add admin fields (paths, arrLink);
|
|
:Push to **userDownloads**;
|
|
elseif (NOT showAll AND matchedUserTag?) then (yes)
|
|
:Build download object (type=movie)
|
|
Add matchedUserTag;
|
|
: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)
|
|
:extractAllTags + extractUserTag(username)
|
|
Build download (type=series, completedAt)
|
|
Add allTags, matchedUserTag, tagBadges if showAll;
|
|
:Push to **userDownloads** if showAll+anyTag or matchedUserTag;
|
|
endif
|
|
endif
|
|
|
|
if (Title matches Radarr **history** record?) then (yes)
|
|
:movie = moviesMap.get(match.movieId)\n|| match.movie;
|
|
if (movie found?) then (yes)
|
|
:extractAllTags + extractUserTag(username)
|
|
Build download (type=movie, completedAt)
|
|
Add allTags, matchedUserTag, tagBadges if showAll;
|
|
:Push to **userDownloads** if showAll+anyTag or matchedUserTag;
|
|
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** (tagMatchesUser):
|
|
1. Exact: tag.toLowerCase() === username
|
|
2. Sanitised: sanitizeTagLabel(tag) === sanitizeTagLabel(username)
|
|
(handles Ombi-mangled email-style usernames)
|
|
|
|
**extractAllTags**: returns all resolved tag labels
|
|
**extractUserTag**: returns the ONE label matching current user
|
|
**buildTagBadges**: classifies each tag against full Emby user
|
|
list → { label, matchedUser: displayName | null }
|
|
end legend
|
|
|
|
@enduml
|