feat(users/Profpatsch/whatcd-resolver): show latest releases

Let’s start improving the main page.

So far, it was just all release groups sorted by weight on a single
page, which was not super helpful (and got kinda large).

The first feature is to show the latest releases that are known.
This is done by torrent group ID. This ID does not always correspond
to the date, but can also be a very old album that gets uploaded,
or (seldomly) a group that gets merged for metadata.

We should think about restricting this to favourites, automatically
marking everything as favourite where we have an album downloaded or
clicked on the artist before, and then selectively allow to un-mark
it after the fact.

An even stronger “not interested” could be used to automatically
reclaim seedbox space once it becomes an issue.

Eventually (after implementing favourites), we should introduce a
job system that automatically updates these entries every few hours.
Maybe even have a “very interested” feature that automatically
downloads everything new for an artist?
And then a “veryvery interested” feature that also buys the thing from
bandcamp lol

Change-Id: I467c350722279ff37150f847f5014d7e0e67e626
Reviewed-on: https://cl.tvl.fyi/c/depot/+/13225
Tested-by: BuildkiteCI
Reviewed-by: Profpatsch <mail@profpatsch.de>
This commit is contained in:
Profpatsch 2025-03-09 15:04:16 +01:00
parent 2d522a9321
commit b6fee0e084
2 changed files with 66 additions and 61 deletions

View file

@ -622,9 +622,12 @@ getTorrentById dat = do
data GetBestTorrentsFilter = GetBestTorrentsFilter data GetBestTorrentsFilter = GetBestTorrentsFilter
{ onlyArtist :: Maybe (Label "artistRedactedId" Int), { onlyArtist :: Maybe (Label "artistRedactedId" Int),
onlyTheseTorrents :: Maybe ([Label "torrentId" Int]), onlyTheseTorrents :: Maybe ([Label "torrentId" Int]),
limitResults :: Maybe Natural limitResults :: Maybe Natural,
ordering :: BestTorrentsOrdering
} }
data BestTorrentsOrdering = BySeedingWeight | ByLastReleases
-- | Find the best torrent for each torrent group (based on the seeding_weight) -- | Find the best torrent for each torrent group (based on the seeding_weight)
getBestTorrents :: getBestTorrents ::
(MonadPostgres m) => (MonadPostgres m) =>
@ -632,7 +635,7 @@ getBestTorrents ::
Transaction m [TorrentData ()] Transaction m [TorrentData ()]
getBestTorrents opts = do getBestTorrents opts = do
queryWith queryWith
[sql| ( [sql|
WITH filtered_torrents AS ( WITH filtered_torrents AS (
SELECT DISTINCT ON (torrent_group) SELECT DISTINCT ON (torrent_group)
id id
@ -669,9 +672,14 @@ getBestTorrents opts = do
FROM filtered_torrents f FROM filtered_torrents f
JOIN redacted.torrents t ON t.id = f.id JOIN redacted.torrents t ON t.id = f.id
JOIN redacted.torrent_groups tg ON tg.id = t.torrent_group JOIN redacted.torrent_groups tg ON tg.id = t.torrent_group
ORDER BY seeding_weight DESC |]
<> case opts.ordering of
BySeedingWeight -> [fmt|ORDER BY seeding_weight DESC|] <> "\n"
ByLastReleases -> [fmt|ORDER BY tg.group_id DESC|] <> "\n"
<> [sql|
LIMIT ?::int LIMIT ?::int
|] |]
)
( do ( do
let (onlyArtistB, onlyArtistId) = case opts.onlyArtist of let (onlyArtistB, onlyArtistId) = case opts.onlyArtist of
Nothing -> (True, 0) Nothing -> (True, 0)

View file

@ -156,8 +156,10 @@ htmlUi = do
res <- t res <- t
(table, settings) <- (table, settings) <-
concurrentlyTraced concurrentlyTraced
( getBestTorrentsTable ( do
(t2 #groupByReleaseType True #limitResults Nothing) d <-
getBestTorrentsData
(t2 #limitResults Nothing #ordering BySeedingWeight)
( Just ( Just
( E21 ( E21
(label @"onlyTheseTorrents" res.newTorrents) (label @"onlyTheseTorrents" res.newTorrents)
@ -170,6 +172,7 @@ htmlUi = do
Int Int
) )
) )
pure $ mkBestTorrentsTableByReleaseType d
) )
(getSettings) (getSettings)
pure $ pure $
@ -376,7 +379,12 @@ htmlUi = do
-- <&> renderJsonld -- <&> renderJsonld
(bestTorrentsTable, settings) <- (bestTorrentsTable, settings) <-
concurrentlyTraced concurrentlyTraced
(getBestTorrentsTable (t2 #groupByReleaseType False #limitResults (Just 1000)) Nothing) ( do
d <- getBestTorrentsData (t2 #limitResults (Just 30) #ordering ByLastReleases) Nothing
pure $ case d & nonEmpty of
Nothing -> [hsx|<h1>Last Releases</h1><p>No torrents found</p>|]
Just d' -> mkBestTorrentsTableSection (lbl #sectionName "Last Releases") d'
)
(getSettings) (getSettings)
-- transmissionTorrentsTable <- lift @Transaction getTransmissionTorrentsTable -- transmissionTorrentsTable <- lift @Transaction getTransmissionTorrentsTable
pure $ pure $
@ -420,7 +428,7 @@ mainHtml' dat = do
{dat.mainContent} {dat.mainContent}
</div> </div>
<!-- refresh the page if the uniqueRunId is different --> <!-- refresh the page if the uniqueRunId is different -->
<input <!-- <input
hidden hidden
type="text" type="text"
id="autorefresh" id="autorefresh"
@ -429,7 +437,7 @@ mainHtml' dat = do
hx-get="/autorefresh" hx-get="/autorefresh"
hx-trigger="every 5s" hx-trigger="every 5s"
hx-swap="none" hx-swap="none"
/> /> -->
|] |]
withAsyncTraced :: (MonadUnliftIO m) => m a -> (Async a -> m b) -> m b withAsyncTraced :: (MonadUnliftIO m) => m a -> (Async a -> m b) -> m b
@ -580,11 +588,11 @@ artistPage dat = runTransaction $ do
(fresh, settings) <- (fresh, settings) <-
concurrentlyTraced concurrentlyTraced
( getBestTorrentsData ( getBestTorrentsData
(label @"limitResults" Nothing) (t2 #limitResults Nothing #ordering BySeedingWeight)
(Just $ E22 (getLabel @"artistRedactedId" dat)) (Just $ E22 (getLabel @"artistRedactedId" dat))
) )
(getSettings) (getSettings)
let torrents = mkBestTorrentsTable (label @"groupByReleaseType" True) fresh let torrents = mkBestTorrentsTableByReleaseType fresh
let returnUrl = let returnUrl =
textToBytesUtf8 $ textToBytesUtf8 $
@ -809,22 +817,6 @@ data ArtistFilter = ArtistFilter
{ onlyArtist :: Maybe (Label "artistId" Text) { onlyArtist :: Maybe (Label "artistId" Text)
} }
getBestTorrentsTable ::
( MonadTransmission m,
MonadThrow m,
MonadLogger m,
MonadPostgres m,
MonadOtel m,
HasField "groupByReleaseType" opts Bool,
HasField "limitResults" opts (Maybe Natural)
) =>
opts ->
Maybe (E2 "onlyTheseTorrents" [Label "torrentId" Int] "artistRedactedId" Int) ->
Transaction m Html
getBestTorrentsTable opts dat = do
fresh <- getBestTorrentsData opts dat
pure $ mkBestTorrentsTable opts fresh
doIfJust :: (Applicative f) => (a -> f ()) -> Maybe a -> f () doIfJust :: (Applicative f) => (a -> f ()) -> Maybe a -> f ()
doIfJust = traverse_ doIfJust = traverse_
@ -834,7 +826,8 @@ getBestTorrentsData ::
MonadLogger m, MonadLogger m,
MonadPostgres m, MonadPostgres m,
MonadOtel m, MonadOtel m,
HasField "limitResults" opts (Maybe Natural) HasField "limitResults" opts (Maybe Natural),
HasField "ordering" opts BestTorrentsOrdering
) => ) =>
opts -> opts ->
Maybe (E2 "onlyTheseTorrents" [Label "torrentId" Int] "artistRedactedId" Int) -> Maybe (E2 "onlyTheseTorrents" [Label "torrentId" Int] "artistRedactedId" Int) ->
@ -846,6 +839,7 @@ getBestTorrentsData opts filters = inSpan' "get torrents table data" $ \span ->
onlyTheseTorrents & doIfJust (\a -> addAttribute span "torrent-filter.ids" (a <&> (getLabel @"torrentId") & showToText & Otel.toAttribute)) onlyTheseTorrents & doIfJust (\a -> addAttribute span "torrent-filter.ids" (a <&> (getLabel @"torrentId") & showToText & Otel.toAttribute))
let limitResults = getField @"limitResults" opts let limitResults = getField @"limitResults" opts
let ordering = opts.ordering
let getBest = getBestTorrents GetBestTorrentsFilter {..} let getBest = getBestTorrents GetBestTorrentsFilter {..}
bestStale :: [TorrentData ()] <- getBest bestStale :: [TorrentData ()] <- getBest
(statusInfo, transmissionStatus) <- (statusInfo, transmissionStatus) <-
@ -894,12 +888,25 @@ getBestTorrentsData opts filters = inSpan' "get torrents table data" $ \span ->
NoTorrentFileYet -> td {torrentStatus = NoTorrentFileYet} NoTorrentFileYet -> td {torrentStatus = NoTorrentFileYet}
) )
mkBestTorrentsTable :: mkBestTorrentsTableByReleaseType ::
(HasField "groupByReleaseType" opts Bool) =>
opts ->
[TorrentData (Label "percentDone" Percentage)] -> [TorrentData (Label "percentDone" Percentage)] ->
Html Html
mkBestTorrentsTable opts fresh = do mkBestTorrentsTableByReleaseType fresh =
fresh
& toList
& groupAllWithComparison ((.releaseType) >$< releaseTypeComparison)
& foldMap
( \ts -> do
let releaseType = ts & NonEmpty.head & (.releaseType.stringKey)
mkBestTorrentsTableSection (lbl #sectionName [fmt|{releaseType}s|]) ts
)
mkBestTorrentsTableSection ::
(HasField "sectionName" opts Text) =>
opts ->
NonEmpty (TorrentData (Label "percentDone" Percentage)) ->
Html
mkBestTorrentsTableSection opts torrents = do
let localTorrent b = case b.torrentStatus of let localTorrent b = case b.torrentStatus of
NoTorrentFileYet -> NoTorrentFileYet ->
[hsx| [hsx|
@ -949,11 +956,9 @@ mkBestTorrentsTable opts fresh = do
</tr> </tr>
|] |]
) )
let section :: NonEmpty (TorrentData (Label "percentDone" Percentage)) -> Html
section rows = do
let releaseType = rows & NonEmpty.head & (.releaseType.stringKey)
[hsx| [hsx|
<h2>{releaseType}s</h2> <h2>{opts.sectionName}</h2>
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
@ -969,23 +974,11 @@ mkBestTorrentsTable opts fresh = do
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{bestRows rows} {bestRows torrents}
</tbody> </tbody>
</table> </table>
|] |]
case fresh & nonEmpty of
Nothing -> [hsx|No torrents found|]
Just fresh' -> do
( if opts.groupByReleaseType
then
fresh'
& toList
& groupAllWithComparison ((.releaseType) >$< releaseTypeComparison)
else [fresh']
)
& foldMap section
mkLinkList :: [T2 "url" Text "content" Html] -> Html mkLinkList :: [T2 "url" Text "content" Html] -> Html
mkLinkList xs = mkLinkList xs =
xs xs
@ -1510,6 +1503,10 @@ t2 :: forall l1 t1 l2 t2. LabelPrx l1 -> t1 -> LabelPrx l2 -> t2 -> T2 l1 t1 l2
{-# INLINE t2 #-} {-# INLINE t2 #-}
t2 LabelPrx a LabelPrx b = T2 (label @l1 a) (label @l2 b) t2 LabelPrx a LabelPrx b = T2 (label @l1 a) (label @l2 b)
t3 :: forall l1 t1 l2 t2 l3 t3. LabelPrx l1 -> t1 -> LabelPrx l2 -> t2 -> LabelPrx l3 -> t3 -> T3 l1 t1 l2 t2 l3 t3
{-# INLINE t3 #-}
t3 LabelPrx a LabelPrx b LabelPrx c = T3 (label @l1 a) (label @l2 b) (label @l3 c)
lbl :: forall l t. LabelPrx l -> t -> Label l t lbl :: forall l t. LabelPrx l -> t -> Label l t
{-# INLINE lbl #-} {-# INLINE lbl #-}
lbl LabelPrx a = label @l a lbl LabelPrx a = label @l a