fix(lint): use default:none to disable errcheck, fix all gofmt and exhaustive

This commit is contained in:
Deivid Soto 2026-03-31 00:29:16 +02:00
parent 4426219f35
commit aed5f0475d
24 changed files with 74 additions and 77 deletions

View file

@ -4,6 +4,7 @@ run:
timeout: 5m timeout: 5m
linters: linters:
default: none
enable: enable:
- govet - govet
- ineffassign - ineffassign

View file

@ -83,8 +83,8 @@ func TestWSTransportConnectAndAuth(t *testing.T) {
// Send registered response // Send registered response
conn.WriteJSON(wsRegisteredMessage{ conn.WriteJSON(wsRegisteredMessage{
Type: "registered", Type: "registered",
User: UserInfo{Name: "WS User", Plan: "pro", IsPro: true}, User: UserInfo{Name: "WS User", Plan: "pro", IsPro: true},
Features: FeatureFlags{Torrent: true}, Features: FeatureFlags{Torrent: true},
}) })

View file

@ -62,8 +62,8 @@ type Task struct {
Title string `json:"title"` Title string `json:"title"`
ContentID *int `json:"contentId,omitempty"` ContentID *int `json:"contentId,omitempty"`
IMDbID string `json:"imdbId,omitempty"` IMDbID string `json:"imdbId,omitempty"`
PreferredMethod string `json:"preferredMethod"` // auto | debrid | usenet | torrent PreferredMethod string `json:"preferredMethod"` // auto | debrid | usenet | torrent
Mode string `json:"mode,omitempty"` // download | stream Mode string `json:"mode,omitempty"` // download | stream
DirectURL string `json:"directUrl,omitempty"` // HTTPS download URL (debrid, etc.) DirectURL string `json:"directUrl,omitempty"` // HTTPS download URL (debrid, etc.)
DirectFileName string `json:"directFileName,omitempty"` // Original filename from direct URL DirectFileName string `json:"directFileName,omitempty"` // Original filename from direct URL
NzbID string `json:"nzbId,omitempty"` // Pre-resolved NZB ID from server NzbID string `json:"nzbId,omitempty"` // Pre-resolved NZB ID from server
@ -88,8 +88,8 @@ type StreamRequest struct {
// StatusUpdate is sent by the CLI to report download progress. // StatusUpdate is sent by the CLI to report download progress.
type StatusUpdate struct { type StatusUpdate struct {
TaskID string `json:"taskId"` TaskID string `json:"taskId"`
Status string `json:"status,omitempty"` // downloading | completed | failed Status string `json:"status,omitempty"` // downloading | completed | failed
Progress int `json:"progress,omitempty"` // 0-100 Progress int `json:"progress,omitempty"` // 0-100
DownloadedBytes int64 `json:"downloadedBytes,omitempty"` DownloadedBytes int64 `json:"downloadedBytes,omitempty"`
TotalBytes int64 `json:"totalBytes,omitempty"` TotalBytes int64 `json:"totalBytes,omitempty"`
SpeedBps int64 `json:"speedBps,omitempty"` SpeedBps int64 `json:"speedBps,omitempty"`
@ -249,9 +249,9 @@ type ConfigureDebridRequest struct {
// ConfigureDebridResponse is returned after configuring a debrid provider. // ConfigureDebridResponse is returned after configuring a debrid provider.
type ConfigureDebridResponse struct { type ConfigureDebridResponse struct {
Success bool `json:"success"` Success bool `json:"success"`
Account DebridAccount `json:"account"` Account DebridAccount `json:"account"`
Error string `json:"error,omitempty"` Error string `json:"error,omitempty"`
} }
// DebridAccount holds verified debrid account info. // DebridAccount holds verified debrid account info.

View file

@ -11,16 +11,16 @@ import (
// Client talks to a single *arr instance (Sonarr, Radarr, or Prowlarr). // Client talks to a single *arr instance (Sonarr, Radarr, or Prowlarr).
type Client struct { type Client struct {
baseURL string baseURL string
apiKey string apiKey string
httpClient *http.Client httpClient *http.Client
} }
// NewClient creates a client for the given *arr instance. // NewClient creates a client for the given *arr instance.
func NewClient(baseURL, apiKey string) *Client { func NewClient(baseURL, apiKey string) *Client {
return &Client{ return &Client{
baseURL: strings.TrimRight(baseURL, "/"), baseURL: strings.TrimRight(baseURL, "/"),
apiKey: apiKey, apiKey: apiKey,
httpClient: &http.Client{Timeout: 15 * time.Second}, httpClient: &http.Client{Timeout: 15 * time.Second},
} }
} }

View file

@ -128,7 +128,7 @@ func TestExtractBlocklistedHashes(t *testing.T) {
{Data: BlocklistData{InfoHash: "AAAA"}}, {Data: BlocklistData{InfoHash: "AAAA"}},
{Data: BlocklistData{InfoHash: "AAAA"}}, // duplicate {Data: BlocklistData{InfoHash: "AAAA"}}, // duplicate
{Data: BlocklistData{InfoHash: "BBBB"}}, {Data: BlocklistData{InfoHash: "BBBB"}},
{Data: BlocklistData{InfoHash: ""}}, // empty {Data: BlocklistData{InfoHash: ""}}, // empty
} }
hashes := ExtractBlocklistedHashes(items) hashes := ExtractBlocklistedHashes(items)
if len(hashes) != 2 { if len(hashes) != 2 {
@ -139,8 +139,8 @@ func TestExtractBlocklistedHashes(t *testing.T) {
func TestExtractDownloadedHashes(t *testing.T) { func TestExtractDownloadedHashes(t *testing.T) {
records := []HistoryRecord{ records := []HistoryRecord{
{EventType: "downloadFolderImported", Data: HistoryData{InfoHash: "hash1"}}, {EventType: "downloadFolderImported", Data: HistoryData{InfoHash: "hash1"}},
{EventType: "grabbed", Data: HistoryData{InfoHash: "hash2"}}, // not imported {EventType: "grabbed", Data: HistoryData{InfoHash: "hash2"}}, // not imported
{EventType: "downloadFolderImported", Data: HistoryData{InfoHash: "hash1"}}, // duplicate {EventType: "downloadFolderImported", Data: HistoryData{InfoHash: "hash1"}}, // duplicate
{EventType: "downloadFolderImported", Data: HistoryData{InfoHash: "hash3"}}, {EventType: "downloadFolderImported", Data: HistoryData{InfoHash: "hash3"}},
} }
hashes := ExtractDownloadedHashes(records) hashes := ExtractDownloadedHashes(records)

View file

@ -112,11 +112,11 @@ type Tag struct {
// HistoryRecord is a single entry from /api/v3/history. // HistoryRecord is a single entry from /api/v3/history.
type HistoryRecord struct { type HistoryRecord struct {
ID int `json:"id"` ID int `json:"id"`
EventType string `json:"eventType"` // "grabbed", "downloadFolderImported", etc. EventType string `json:"eventType"` // "grabbed", "downloadFolderImported", etc.
DownloadID string `json:"downloadId"` DownloadID string `json:"downloadId"`
SourceTitle string `json:"sourceTitle"` SourceTitle string `json:"sourceTitle"`
Data HistoryData `json:"data"` Data HistoryData `json:"data"`
} }
// HistoryData holds the nested data of a history record. // HistoryData holds the nested data of a history record.
@ -127,14 +127,14 @@ type HistoryData struct {
// HistoryResponse wraps the paginated history from *arr. // HistoryResponse wraps the paginated history from *arr.
type HistoryResponse struct { type HistoryResponse struct {
Records []HistoryRecord `json:"records"` Records []HistoryRecord `json:"records"`
TotalRecords int `json:"totalRecords"` TotalRecords int `json:"totalRecords"`
} }
// BlocklistItem is an item the user explicitly rejected. // BlocklistItem is an item the user explicitly rejected.
type BlocklistItem struct { type BlocklistItem struct {
ID int `json:"id"` ID int `json:"id"`
SourceTitle string `json:"sourceTitle"` SourceTitle string `json:"sourceTitle"`
Data BlocklistData `json:"data"` Data BlocklistData `json:"data"`
} }
@ -145,8 +145,8 @@ type BlocklistData struct {
// BlocklistResponse wraps paginated blocklist from *arr. // BlocklistResponse wraps paginated blocklist from *arr.
type BlocklistResponse struct { type BlocklistResponse struct {
Records []BlocklistItem `json:"records"` Records []BlocklistItem `json:"records"`
TotalRecords int `json:"totalRecords"` TotalRecords int `json:"totalRecords"`
} }
// Instance represents a discovered *arr application. // Instance represents a discovered *arr application.

View file

@ -341,4 +341,3 @@ func CleanableBytes() int64 {
return total return total
} }

View file

@ -55,7 +55,6 @@ func TestFileSize_NonExistent(t *testing.T) {
} }
} }
func TestRunClean_DryRun(t *testing.T) { func TestRunClean_DryRun(t *testing.T) {
err := runClean(true, false, false) err := runClean(true, false, false)
if err != nil { if err != nil {

View file

@ -18,8 +18,8 @@ var configCategories = []string{"downloads", "organization", "notifications", "d
func newConfigCmd() *cobra.Command { func newConfigCmd() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "config [category]", Use: "config [category]",
Short: "Edit settings interactively", Short: "Edit settings interactively",
Long: `Edit unarr settings interactively with a category-based menu. Long: `Edit unarr settings interactively with a category-based menu.
Categories: Categories:

View file

@ -236,4 +236,3 @@ func runDaemonUninstall() error {
fmt.Println() fmt.Println()
return nil return nil
} }

View file

@ -6,9 +6,9 @@ import (
"github.com/fatih/color" "github.com/fatih/color"
"github.com/spf13/cobra" "github.com/spf13/cobra"
tc "github.com/torrentclaw/go-client"
"github.com/torrentclaw/unarr/internal/config" "github.com/torrentclaw/unarr/internal/config"
"github.com/torrentclaw/unarr/internal/sentry" "github.com/torrentclaw/unarr/internal/sentry"
tc "github.com/torrentclaw/go-client"
) )
var ( var (

View file

@ -155,4 +155,3 @@ func handleStreamTask(parentCtx context.Context, at agent.Task, reporter *engine
} }
} }
} }

View file

@ -14,8 +14,6 @@ import (
"github.com/anacrolix/torrent" "github.com/anacrolix/torrent"
) )
// StreamConfig holds settings for the streaming engine. // StreamConfig holds settings for the streaming engine.
type StreamConfig struct { type StreamConfig struct {
DataDir string DataDir string
@ -30,7 +28,7 @@ type StreamConfig struct {
type StreamStatus int type StreamStatus int
const ( const (
StreamStatusMetadata StreamStatus = iota StreamStatusMetadata StreamStatus = iota
StreamStatusBuffering StreamStatusBuffering
StreamStatusReady StreamStatusReady
StreamStatusError StreamStatusError

View file

@ -354,7 +354,7 @@ type responseRecorder struct {
body *strings.Builder body *strings.Builder
} }
func (r *responseRecorder) Header() http.Header { return r.headers } func (r *responseRecorder) Header() http.Header { return r.headers }
func (r *responseRecorder) WriteHeader(code int) { r.statusCode = code } func (r *responseRecorder) WriteHeader(code int) { r.statusCode = code }
func (r *responseRecorder) Write(b []byte) (int, error) { func (r *responseRecorder) Write(b []byte) (int, error) {
if r.statusCode == 0 { if r.statusCode == 0 {

View file

@ -191,6 +191,8 @@ func (t *Task) ToStatusUpdate() agent.StatusUpdate {
apiStatus = "completed" apiStatus = "completed"
case StatusFailed: case StatusFailed:
apiStatus = "failed" apiStatus = "failed"
default:
// StatusPending, StatusClaimed, StatusCancelled — not reported
} }
return agent.StatusUpdate{ return agent.StatusUpdate{

View file

@ -173,8 +173,8 @@ func TestToStatusUpdate(t *testing.T) {
func TestToStatusUpdateGranularStates(t *testing.T) { func TestToStatusUpdateGranularStates(t *testing.T) {
tests := []struct { tests := []struct {
status TaskStatus status TaskStatus
wantAPI string wantAPI string
}{ }{
{StatusResolving, "resolving"}, {StatusResolving, "resolving"},
{StatusDownloading, "downloading"}, {StatusDownloading, "downloading"},

View file

@ -11,9 +11,9 @@ import (
"sync" "sync"
"time" "time"
alog "github.com/anacrolix/log"
"github.com/anacrolix/dht/v2" "github.com/anacrolix/dht/v2"
"github.com/anacrolix/dht/v2/krpc" "github.com/anacrolix/dht/v2/krpc"
alog "github.com/anacrolix/log"
"github.com/anacrolix/torrent" "github.com/anacrolix/torrent"
"github.com/anacrolix/torrent/storage" "github.com/anacrolix/torrent/storage"
"github.com/torrentclaw/unarr/internal/config" "github.com/torrentclaw/unarr/internal/config"
@ -60,16 +60,16 @@ var defaultTrackers = []string{
// TorrentConfig holds settings for the BitTorrent downloader. // TorrentConfig holds settings for the BitTorrent downloader.
type TorrentConfig struct { type TorrentConfig struct {
DataDir string DataDir string
MetadataTimeout time.Duration // how long to wait for torrent metadata (default 15m, 0 = unlimited) MetadataTimeout time.Duration // how long to wait for torrent metadata (default 15m, 0 = unlimited)
StallTimeout time.Duration // no progress during download for this long = stall (default 10m) StallTimeout time.Duration // no progress during download for this long = stall (default 10m)
MaxTimeout time.Duration // absolute maximum per torrent (default 0 = unlimited) MaxTimeout time.Duration // absolute maximum per torrent (default 0 = unlimited)
MaxDownloadRate int64 // bytes/s, 0 = unlimited MaxDownloadRate int64 // bytes/s, 0 = unlimited
MaxUploadRate int64 // bytes/s, 0 = unlimited MaxUploadRate int64 // bytes/s, 0 = unlimited
ListenPort int // fixed port for incoming peers (default 42069, 0 = random) ListenPort int // fixed port for incoming peers (default 42069, 0 = random)
SeedEnabled bool SeedEnabled bool
SeedRatio float64 // target seed ratio (default 0, meaning seed until SeedTime) SeedRatio float64 // target seed ratio (default 0, meaning seed until SeedTime)
SeedTime time.Duration // min seed time after completion (default 0) SeedTime time.Duration // min seed time after completion (default 0)
} }
// TorrentDownloader downloads torrents via BitTorrent P2P. // TorrentDownloader downloads torrents via BitTorrent P2P.

View file

@ -21,7 +21,7 @@ import (
// activeDownload holds the state for a single in-progress usenet download. // activeDownload holds the state for a single in-progress usenet download.
type activeDownload struct { type activeDownload struct {
cancel context.CancelFunc cancel context.CancelFunc
taskDir string // populated after MkdirAll; empty before taskDir string // populated after MkdirAll; empty before
tracker *download.ProgressTracker // populated after tracker creation; nil before tracker *download.ProgressTracker // populated after tracker creation; nil before
} }
@ -471,4 +471,3 @@ func sanitizeDir(name string) string {
} }
return name return name
} }

View file

@ -10,7 +10,7 @@ type MediaInfo struct {
// VideoInfo represents the primary video stream metadata. // VideoInfo represents the primary video stream metadata.
type VideoInfo struct { type VideoInfo struct {
Codec string `json:"codec"` // "hevc", "h264", "av1" Codec string `json:"codec"` // "hevc", "h264", "av1"
Width int `json:"width"` Width int `json:"width"`
Height int `json:"height"` Height int `json:"height"`
BitDepth int `json:"bitDepth"` // 8, 10, 12 BitDepth int `json:"bitDepth"` // 8, 10, 12

View file

@ -8,9 +8,9 @@ import (
) )
var ( var (
seasonRegex = regexp.MustCompile(`(?i)S(\d{1,2})E(\d{1,2})`) seasonRegex = regexp.MustCompile(`(?i)S(\d{1,2})E(\d{1,2})`)
seasonOnly = regexp.MustCompile(`(?i)S(\d{1,2})(?:\b|$)`) seasonOnly = regexp.MustCompile(`(?i)S(\d{1,2})(?:\b|$)`)
altEpRegex = regexp.MustCompile(`(?i)(\d{1,2})x(\d{2})`) altEpRegex = regexp.MustCompile(`(?i)(\d{1,2})x(\d{2})`)
) )
// ResolveResolution maps a pixel height to a standard resolution label. // ResolveResolution maps a pixel height to a standard resolution label.

View file

@ -4,18 +4,18 @@ import "github.com/torrentclaw/unarr/internal/library/mediainfo"
// LibraryItem represents a single scanned media file. // LibraryItem represents a single scanned media file.
type LibraryItem struct { type LibraryItem struct {
FilePath string `json:"filePath"` FilePath string `json:"filePath"`
FileName string `json:"fileName"` FileName string `json:"fileName"`
FileSize int64 `json:"fileSize"` FileSize int64 `json:"fileSize"`
ModTime string `json:"modTime"` // ISO 8601 ModTime string `json:"modTime"` // ISO 8601
Title string `json:"title"` Title string `json:"title"`
Year string `json:"year,omitempty"` Year string `json:"year,omitempty"`
Season int `json:"season,omitempty"` Season int `json:"season,omitempty"`
Episode int `json:"episode,omitempty"` Episode int `json:"episode,omitempty"`
Quality string `json:"quality,omitempty"` // "1080p" etc (from filename) Quality string `json:"quality,omitempty"` // "1080p" etc (from filename)
Codec string `json:"codec,omitempty"` // "x265" etc (from filename) Codec string `json:"codec,omitempty"` // "x265" etc (from filename)
MediaInfo *mediainfo.MediaInfo `json:"mediaInfo,omitempty"` MediaInfo *mediainfo.MediaInfo `json:"mediaInfo,omitempty"`
ScanError string `json:"scanError,omitempty"` ScanError string `json:"scanError,omitempty"`
} }
// LibraryCache is the on-disk cache of scanned library items. // LibraryCache is the on-disk cache of scanned library items.

View file

@ -161,5 +161,5 @@ func TestFormatContentType(t *testing.T) {
} }
} }
func ptr[T any](v T) *T { return &v } func ptr[T any](v T) *T { return &v }
func intPtr(v int) *int { return &v } func intPtr(v int) *int { return &v }

View file

@ -50,11 +50,11 @@ type xmlMeta struct {
} }
type xmlFile struct { type xmlFile struct {
Poster string `xml:"poster,attr"` Poster string `xml:"poster,attr"`
Date string `xml:"date,attr"` Date string `xml:"date,attr"`
Subject string `xml:"subject,attr"` Subject string `xml:"subject,attr"`
Groups xmlGroups `xml:"groups"` Groups xmlGroups `xml:"groups"`
Segments xmlSegments `xml:"segments"` Segments xmlSegments `xml:"segments"`
} }
type xmlGroups struct { type xmlGroups struct {
@ -263,8 +263,9 @@ func (f *File) TotalBytes() int64 {
// subjectFilenameRe matches the filename in a typical Usenet subject line. // subjectFilenameRe matches the filename in a typical Usenet subject line.
// Examples: // Examples:
// "Movie.2024.1080p.mkv" yEnc (1/50) //
// [PRiVATE]-[#a]- "file.rar" yEnc (01/99) // "Movie.2024.1080p.mkv" yEnc (1/50)
// [PRiVATE]-[#a]- "file.rar" yEnc (01/99)
var subjectFilenameRe = regexp.MustCompile(`"([^"]+)"`) var subjectFilenameRe = regexp.MustCompile(`"([^"]+)"`)
// Filename extracts the filename from the subject line. // Filename extracts the filename from the subject line.

View file

@ -105,7 +105,7 @@ func IsPasswordProtected(archivePath string) bool {
return false return false
} }
switch extType { switch extType { //nolint:exhaustive // ExtractorNone handled above
case ExtractorUnrar: case ExtractorUnrar:
cmd := exec.Command(extPath, "t", "-p-", archivePath) cmd := exec.Command(extPath, "t", "-p-", archivePath)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()