diff --git a/internal/database/release.go b/internal/database/release.go index 7c6a6e2..38cfff9 100644 --- a/internal/database/release.go +++ b/internal/database/release.go @@ -211,7 +211,7 @@ func (repo *ReleaseRepo) findReleases(ctx context.Context, tx *Tx, params domain } queryBuilder := repo.db.squirrel. - Select("r.id", "r.filter_status", "r.rejections", "r.indexer", "r.filter", "r.protocol", "r.info_url", "r.download_url", "r.title", "r.torrent_name", "r.size", "r.timestamp", + Select("r.id", "r.filter_status", "r.rejections", "r.indexer", "r.filter", "r.protocol", "r.info_url", "r.download_url", "r.title", "r.torrent_name", "r.size", "r.category", "r.season", "r.episode", "r.year", "r.resolution", "r.source", "r.codec", "r.container", "r.release_group", "r.timestamp", "ras.id", "ras.status", "ras.action", "ras.action_id", "ras.type", "ras.client", "ras.filter", "ras.filter_id", "ras.release_id", "ras.rejections", "ras.timestamp"). Column(sq.Alias(countQuery, "page_total")). From("release r"). @@ -245,17 +245,22 @@ func (repo *ReleaseRepo) findReleases(ctx context.Context, tx *Tx, params domain var rls domain.Release var ras domain.ReleaseActionStatus - var rlsindexer, rlsfilter, infoUrl, downloadUrl sql.NullString + var rlsindexer, rlsfilter, infoUrl, downloadUrl, codec sql.NullString var rasId, rasFilterId, rasReleaseId, rasActionId sql.NullInt64 var rasStatus, rasAction, rasType, rasClient, rasFilter sql.NullString var rasRejections []sql.NullString var rasTimestamp sql.NullTime - if err := rows.Scan(&rls.ID, &rls.FilterStatus, pq.Array(&rls.Rejections), &rlsindexer, &rlsfilter, &rls.Protocol, &infoUrl, &downloadUrl, &rls.Title, &rls.TorrentName, &rls.Size, &rls.Timestamp, &rasId, &rasStatus, &rasAction, &rasActionId, &rasType, &rasClient, &rasFilter, &rasFilterId, &rasReleaseId, pq.Array(&rasRejections), &rasTimestamp, &countItems); err != nil { + if err := rows.Scan(&rls.ID, &rls.FilterStatus, pq.Array(&rls.Rejections), &rlsindexer, &rlsfilter, &rls.Protocol, &infoUrl, &downloadUrl, &rls.Title, &rls.TorrentName, &rls.Size, &rls.Category, &rls.Season, &rls.Episode, &rls.Year, &rls.Resolution, &rls.Source, &codec, &rls.Container, &rls.Group, &rls.Timestamp, &rasId, &rasStatus, &rasAction, &rasActionId, &rasType, &rasClient, &rasFilter, &rasFilterId, &rasReleaseId, pq.Array(&rasRejections), &rasTimestamp, &countItems); err != nil { return res, 0, 0, errors.Wrap(err, "error scanning row") } + //for _, codec := range codecs { + // rls.Codec = append(rls.Codec, codec.String) + // + //} + ras.ID = rasId.Int64 ras.Status = domain.ReleasePushStatus(rasStatus.String) ras.Action = rasAction.String @@ -291,6 +296,7 @@ func (repo *ReleaseRepo) findReleases(ctx context.Context, tx *Tx, params domain rls.ActionStatus = make([]domain.ReleaseActionStatus, 0) rls.InfoURL = infoUrl.String rls.DownloadURL = downloadUrl.String + rls.Codec = strings.Split(codec.String, ",") // only add ActionStatus if it's not empty if ras.ID > 0 { diff --git a/internal/domain/release.go b/internal/domain/release.go index bca09c7..75e96f7 100644 --- a/internal/domain/release.go +++ b/internal/domain/release.go @@ -59,7 +59,7 @@ type Release struct { TorrentTmpFile string `json:"-"` TorrentDataRawBytes []byte `json:"-"` TorrentHash string `json:"-"` - TorrentName string `json:"torrent_name"` // full release name + TorrentName string `json:"name"` // full release name Size uint64 `json:"size"` Title string `json:"title"` // Parsed title Description string `json:"-"` @@ -538,32 +538,6 @@ func (r *Release) HasMagnetUri() bool { return r.MagnetURI != "" } -type magnetRoundTripper struct{} - -func (rt *magnetRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { - if r.URL.Scheme == "magnet" { - responseBody := r.URL.String() - respReader := io.NopCloser(strings.NewReader(responseBody)) - - resp := &http.Response{ - Status: http.StatusText(http.StatusOK), - StatusCode: http.StatusOK, - Body: respReader, - ContentLength: int64(len(responseBody)), - Header: map[string][]string{ - "Content-Type": {"text/plain"}, - "Location": {responseBody}, - }, - Proto: "HTTP/2.0", - ProtoMajor: 2, - } - - return resp, nil - } - - return http.DefaultTransport.RoundTrip(r) -} - func (r *Release) ResolveMagnetUri(ctx context.Context) error { if r.MagnetURI == "" { return nil diff --git a/web/src/components/data-table/Cells.tsx b/web/src/components/data-table/Cells.tsx index b6a71c4..fab165b 100644 --- a/web/src/components/data-table/Cells.tsx +++ b/web/src/components/data-table/Cells.tsx @@ -7,33 +7,110 @@ import * as React from "react"; import { toast } from "react-hot-toast"; import { formatDistanceToNowStrict } from "date-fns"; import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { CellProps } from "react-table"; import { ArrowPathIcon, CheckIcon } from "@heroicons/react/24/solid"; -import { ArrowDownTrayIcon, ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline"; import { ExternalLink } from "../ExternalLink"; -import { ClockIcon, XMarkIcon, NoSymbolIcon } from "@heroicons/react/24/outline"; +import { + ClockIcon, + XMarkIcon, + NoSymbolIcon, + ArrowDownTrayIcon, + ArrowTopRightOnSquareIcon, DocumentTextIcon +} from "@heroicons/react/24/outline"; import { APIClient } from "@api/APIClient"; -import { classNames, simplifyDate } from "@utils"; +import {classNames, humanFileSize, simplifyDate} from "@utils"; import { filterKeys } from "@screens/filters/List"; import Toast from "@components/notifications/Toast"; import { RingResizeSpinner } from "@components/Icons"; import { Tooltip } from "@components/tooltips/Tooltip"; -interface CellProps { - value: string; -} - -interface LinksCellProps { - value: Release; -} - -export const AgeCell = ({ value }: CellProps) => ( -
- {formatDistanceToNowStrict(new Date(value), { addSuffix: false })} +export const NameCell = (props: CellProps) => ( +
+
+ + {String(props.cell.value)} + +
+ Category: {props.row.original.category} + Size: {humanFileSize(props.row.original.size)} + Misc: {`${props.row.original.resolution} ${props.row.original.source} ${props.row.original.codec ?? ""} ${props.row.original.container}`} +
+
); -export const IndexerCell = ({ value }: CellProps) => ( +export const LinksCell = (props: CellProps) => { + return ( +
+
+ } + title="Details" + > +
+ {props.row.original.name} + {props.row.original.indexer} + {props.row.original.protocol} + {props.row.original.implementation} + {props.row.original.category} + {props.row.original.uploader} + {humanFileSize(props.row.original.size)} + {props.row.original.title} + {props.row.original.year > 0 && {props.row.original.year.toString()}} + {props.row.original.season > 0 && + {props.row.original.season.toString()}} + {props.row.original.episode > 0 && + {props.row.original.episode.toString()}} + {props.row.original.resolution} + {props.row.original.source} + {props.row.original.codec} + {props.row.original.hdr} + {props.row.original.group} + {props.row.original.container} + {props.row.original.origin} +
+
+
+ {props.row.original.download_url && ( + + + )} + {props.row.original.info_url && ( + + + )} +
+ ); +}; + +export const AgeCell = ({value}: CellProps) => ( +
+ {formatDistanceToNowStrict(new Date(value), {addSuffix: false})} +
+); + +export const IndexerCell = ({value}: CellProps) => (
(
); -export const TitleCell = ({ value }: CellProps) => ( +export const TitleCell = ({value}: CellProps) => (
(
); -export const LinksCell = ({ value }: LinksCellProps) => { - return ( -
- {value.download_url && ( - - - )} - {value.info_url && ( - - - )} -
- ); -}; diff --git a/web/src/components/data-table/index.tsx b/web/src/components/data-table/index.tsx index d146d4d..0a12b33 100644 --- a/web/src/components/data-table/index.tsx +++ b/web/src/components/data-table/index.tsx @@ -4,4 +4,4 @@ */ export { Button, PageButton } from "./Buttons"; -export { AgeCell, IndexerCell, TitleCell, ReleaseStatusCell, LinksCell } from "./Cells"; +export { AgeCell, IndexerCell, NameCell, TitleCell, ReleaseStatusCell, LinksCell } from "./Cells"; diff --git a/web/src/screens/dashboard/ActivityTable.tsx b/web/src/screens/dashboard/ActivityTable.tsx index 17e2572..76c7fd1 100644 --- a/web/src/screens/dashboard/ActivityTable.tsx +++ b/web/src/screens/dashboard/ActivityTable.tsx @@ -168,7 +168,7 @@ export const ActivityTable = () => { }, { Header: "Release", - accessor: "torrent_name", + accessor: "name", Cell: DataTable.TitleCell }, { @@ -183,7 +183,7 @@ export const ActivityTable = () => { Filter: SelectColumnFilter, filter: "includes" } - ], []); + ] as Column[], []); const { isLoading, data } = useSuspenseQuery({ queryKey: ["dash_recent_releases"], @@ -213,7 +213,7 @@ export const ActivityTable = () => { const randomNames = RandomLinuxIsos(data.data.length); const newData: Release[] = data.data.map((item, index) => ({ ...item, - torrent_name: `${randomNames[index]}.iso`, + name: `${randomNames[index]}.iso`, indexer: index % 2 === 0 ? "distrowatch" : "linuxtracker" })); setModifiedData(newData); diff --git a/web/src/screens/releases/ReleaseTable.tsx b/web/src/screens/releases/ReleaseTable.tsx index 6adf6cb..583bb2a 100644 --- a/web/src/screens/releases/ReleaseTable.tsx +++ b/web/src/screens/releases/ReleaseTable.tsx @@ -6,7 +6,7 @@ import React, { useState } from "react"; import { useLocation } from "react-router-dom"; import { useSuspenseQuery } from "@tanstack/react-query"; -import { CellProps, Column, useFilters, usePagination, useSortBy, useTable } from "react-table"; +import { Column, useFilters, usePagination, useSortBy, useTable } from "react-table"; import { ChevronDoubleLeftIcon, ChevronDoubleRightIcon, @@ -23,8 +23,6 @@ import * as Icons from "@components/Icons"; import * as DataTable from "@components/data-table"; import { IndexerSelectColumnFilter, PushStatusSelectColumnFilter, SearchColumnFilter } from "./Filters"; -import { classNames } from "@utils"; -import { Tooltip } from "@components/tooltips/Tooltip"; export const releaseKeys = { all: ["releases"] as const, @@ -94,27 +92,8 @@ export const ReleaseTable = () => { }, { Header: "Release", - accessor: "torrent_name", - Cell: (props: CellProps) => { - return ( -
- - - {String(props.cell.value)} - - -
- ); - }, + accessor: "name", + Cell: DataTable.NameCell, Filter: SearchColumnFilter }, { @@ -156,7 +135,7 @@ export const ReleaseTable = () => { const randomNames = RandomLinuxIsos(data.data.length); const newData: Release[] = data.data.map((item, index) => ({ ...item, - torrent_name: `${randomNames[index]}.iso`, + name: `${randomNames[index]}.iso`, indexer: index % 2 === 0 ? "distrowatch" : "linuxtracker" })); setModifiedData(newData); diff --git a/web/src/types/Release.d.ts b/web/src/types/Release.d.ts index da33b2b..7dd03da 100644 --- a/web/src/types/Release.d.ts +++ b/web/src/types/Release.d.ts @@ -10,11 +10,27 @@ interface Release { indexer: string; filter: string; protocol: string; + implementation: string; + name: string; title: string; size: number; raw: string; info_url: string; download_url: string; + category: string; + group: string; + season: number; + episode: number; + year: number; + resolution: string; + codec: string; + source: string; + container: string; + hdr: string; + uploader: string; + origin: string; + // freeleech: boolean; + // freeleech_percent:number; timestamp: Date action_status: ReleaseActionStatus[] } diff --git a/web/src/utils/index.ts b/web/src/utils/index.ts index c98b44e..996501a 100644 --- a/web/src/utils/index.ts +++ b/web/src/utils/index.ts @@ -84,6 +84,34 @@ export const get = (obj: T, path: string|Array, defValue?: string) => { return result === undefined ? defValue : result; }; +const UNITS = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte', 'petabyte'] +const BYTES_PER_KB = 1000 + + +/** + * Format bytes as human-readable text. + * + * @param sizeBytes Number of bytes. + * + * @return Formatted string. + */ +export function humanFileSize(sizeBytes: number | bigint): string { + let size = Math.abs(Number(sizeBytes)) + + let u = 0 + while (size >= BYTES_PER_KB && u < UNITS.length - 1) { + size /= BYTES_PER_KB + ++u + } + + return new Intl.NumberFormat([], { + style: 'unit', + unit: UNITS[u], + unitDisplay: 'short', + maximumFractionDigits: 1, + }).format(size) +} + export const RandomLinuxIsos = (count: number) => { const linuxIsos = [ "ubuntu-20.04.4-lts-focal-fossa-desktop-amd64-secure-boot",