From cb759bd07d774e230374e7778bfc4c5ed8dabb78 Mon Sep 17 00:00:00 2001 From: nuxen Date: Sat, 1 Mar 2025 17:26:37 +0100 Subject: [PATCH] refactor: project structure & func/var naming (#170) * refactor: project structure & func/var naming * chore: update header year * ci(goreleaser): fix deprecation * ci(goreleaser): fix typo and version --- .github/workflows/release.yml | 4 +- .gitignore | 1 + .goreleaser.yaml | 12 +- cmd/genToken.go | 2 +- cmd/pack.go | 6 +- cmd/parse.go | 6 +- cmd/root.go | 2 +- cmd/start.go | 4 +- cmd/test.go | 2 +- cmd/version.go | 10 +- go.mod | 69 ++++---- go.sum | 146 ++++++++-------- internal/api/token.go | 2 +- internal/buildinfo/buildinfo.go | 2 +- internal/domain/http.go | 2 +- internal/domain/release.go | 2 +- internal/files/hardlink.go | 25 +++ internal/format/format.go | 45 +++++ .../format_test.go} | 13 +- internal/http/processor.go | 162 +++++++++--------- internal/http/webhook.go | 2 +- internal/{utils => metadata}/tvmaze.go | 13 +- internal/metadata/tvmaze_test.go | 86 ++++++++++ internal/payload/payload.go | 8 +- internal/release/release.go | 24 +-- internal/{utils => slices}/slices.go | 12 +- internal/{utils => slices}/slices_test.go | 14 +- internal/torrents/decode.go | 2 +- internal/torrents/torrents.go | 8 +- internal/utils/formatting.go | 46 ----- internal/utils/hardlink.go | 25 --- internal/utils/tvmaze_test.go | 74 -------- main.go | 2 +- 33 files changed, 427 insertions(+), 406 deletions(-) create mode 100644 internal/files/hardlink.go create mode 100644 internal/format/format.go rename internal/{utils/formatting_test.go => format/format_test.go} (92%) rename internal/{utils => metadata}/tvmaze.go (64%) create mode 100644 internal/metadata/tvmaze_test.go rename internal/{utils => slices}/slices.go (72%) rename internal/{utils => slices}/slices_test.go (90%) delete mode 100644 internal/utils/formatting.go delete mode 100644 internal/utils/hardlink.go delete mode 100644 internal/utils/tvmaze_test.go diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 07b1c57..36a2226 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -73,7 +73,7 @@ jobs: uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser - version: latest + version: "~> v2" args: release --clean --skip=validate,publish --parallelism 5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -83,7 +83,7 @@ jobs: uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser - version: latest + version: "~> v2" args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 8dfb10b..ab5e31c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.dll *.so *.dylib +.DS_Store* # Test binary, built with `go test -c` *.test diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 93f83c4..0a05436 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,3 +1,5 @@ +version: 2 + before: hooks: - go mod tidy @@ -39,7 +41,7 @@ archives: - seasonpackarr format_overrides: - goos: windows - format: zip + formats: [ "zip" ] files: - none* name_template: >- @@ -59,11 +61,11 @@ release: - `docker pull ghcr.io/nuxencs/seasonpackarr:{{ .Tag }}` ## What to do next? - + - Read the [documentation](https://github.com/nuxencs/seasonpackarr#readme) checksum: - name_template: '{{ .ProjectName }}_{{ .Version }}_checksums.txt' + name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt" changelog: sort: asc @@ -74,10 +76,10 @@ changelog: - Merge remote-tracking branch - Merge branch groups: - - title: 'New Features' + - title: "New Features" regexp: "^.*feat[(\\w)]*:+.*$" order: 0 - - title: 'Bug fixes' + - title: "Bug fixes" regexp: "^.*fix[(\\w)]*:+.*$" order: 10 - title: Other work diff --git a/cmd/genToken.go b/cmd/genToken.go index b8bb0ef..dbca0cc 100644 --- a/cmd/genToken.go +++ b/cmd/genToken.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later package cmd diff --git a/cmd/pack.go b/cmd/pack.go index f476231..00fb272 100644 --- a/cmd/pack.go +++ b/cmd/pack.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later package cmd @@ -24,13 +24,13 @@ var packCmd = &cobra.Command{ rlsName = args[0] - body, err := payload.CompilePackPayload(rlsName, clientName) + body, err := payload.CompilePack(rlsName, clientName) if err != nil { fmt.Println(err.Error()) return } - err = payload.ExecRequest(fmt.Sprintf("http://%s:%d/api/pack", host, port), body, apiKey) + err = payload.Exec(fmt.Sprintf("http://%s:%d/api/pack", host, port), body, apiKey) if err != nil { fmt.Println(err.Error()) return diff --git a/cmd/parse.go b/cmd/parse.go index ada2e71..0e99a5f 100644 --- a/cmd/parse.go +++ b/cmd/parse.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later package cmd @@ -62,13 +62,13 @@ var parseCmd = &cobra.Command{ } } - body, err = payload.CompileParsePayload(rlsName, torrentBytes, clientName) + body, err = payload.CompileParse(rlsName, torrentBytes, clientName) if err != nil { fmt.Println(err.Error()) return } - err = payload.ExecRequest(fmt.Sprintf("http://%s:%d/api/parse", host, port), body, apiKey) + err = payload.Exec(fmt.Sprintf("http://%s:%d/api/parse", host, port), body, apiKey) if err != nil { fmt.Println(err.Error()) return diff --git a/cmd/root.go b/cmd/root.go index f2fae1d..4b434c1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later package cmd diff --git a/cmd/start.go b/cmd/start.go index c40e50a..3febe25 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later package cmd @@ -63,7 +63,7 @@ var startCmd = &cobra.Command{ select { case sig := <-sigCh: - log.Info().Msgf("received signal: %q, shutting down server.", sig.String()) + log.Info().Msgf("received signal: %q, shutting down server.", sig) os.Exit(0) case err := <-errorChannel: diff --git a/cmd/test.go b/cmd/test.go index ea163f7..86bdca4 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later package cmd diff --git a/cmd/version.go b/cmd/version.go index 75bdba2..c62de6f 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later package cmd @@ -6,7 +6,7 @@ package cmd import ( "encoding/json" "fmt" - netHTTP "net/http" + "net/http" "os" "time" @@ -24,13 +24,13 @@ var versionCmd = &cobra.Command{ fmt.Printf("Version: %v\nCommit: %v\nBuild date: %v\n", buildinfo.Version, buildinfo.Commit, buildinfo.Date) // get the latest release tag from api - client := netHTTP.Client{ + client := http.Client{ Timeout: 10 * time.Second, } resp, err := client.Get("https://api.github.com/repos/nuxencs/seasonpackarr/releases/latest") if err != nil { - if errors.Is(err, netHTTP.ErrHandlerTimeout) { + if errors.Is(err, http.ErrHandlerTimeout) { fmt.Println("Server timed out while fetching latest release from api") } else { fmt.Printf("Failed to fetch latest release from api: %v\n", err) @@ -40,7 +40,7 @@ var versionCmd = &cobra.Command{ defer resp.Body.Close() // api returns 500 instead of 404 here - if resp.StatusCode == netHTTP.StatusNotFound || resp.StatusCode == netHTTP.StatusInternalServerError { + if resp.StatusCode == http.StatusNotFound || resp.StatusCode == http.StatusInternalServerError { fmt.Print("No release found") os.Exit(1) } diff --git a/go.mod b/go.mod index d804f8c..72a75d8 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,22 @@ module github.com/nuxencs/seasonpackarr go 1.23.0 require ( - github.com/anacrolix/torrent v1.57.1 + github.com/anacrolix/torrent v1.58.1 github.com/autobrr/go-qbittorrent v1.11.0 - github.com/dlclark/regexp2 v1.11.4 + github.com/dlclark/regexp2 v1.11.5 github.com/fsnotify/fsnotify v1.8.0 - github.com/gin-contrib/cors v1.7.2 - github.com/gin-contrib/requestid v1.0.3 + github.com/gin-contrib/cors v1.7.3 + github.com/gin-contrib/requestid v1.0.4 github.com/gin-gonic/gin v1.10.0 github.com/google/uuid v1.6.0 github.com/moistari/rls v0.5.12 github.com/mrobinsn/go-tvmaze v1.2.1 github.com/pkg/errors v0.9.1 - github.com/puzpuzpuz/xsync/v3 v3.4.0 + github.com/puzpuzpuz/xsync/v3 v3.5.1 github.com/rs/zerolog v1.33.0 - github.com/spf13/cobra v1.8.1 + github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.19.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) @@ -26,59 +26,58 @@ require ( github.com/Masterminds/semver v1.5.0 // indirect github.com/anacrolix/generics v0.0.3-0.20240902042256-7fb2702ef0ca // indirect github.com/anacrolix/missinggo v1.3.0 // indirect - github.com/anacrolix/missinggo/v2 v2.7.4 // indirect + github.com/anacrolix/missinggo/v2 v2.8.0 // indirect github.com/avast/retry-go v3.0.0+incompatible // indirect github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect - github.com/bytedance/sonic v1.12.2 // indirect - github.com/bytedance/sonic/loader v0.2.0 // indirect - github.com/cloudwego/base64x v0.1.4 // indirect - github.com/cloudwego/iasm v0.2.0 // indirect + github.com/bytedance/sonic v1.12.9 // indirect + github.com/bytedance/sonic/loader v0.2.3 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/gabriel-vasile/mimetype v1.4.5 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gin-contrib/sse v1.0.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.22.0 // indirect - github.com/goccy/go-json v0.10.3 // indirect + github.com/go-playground/validator/v10 v10.25.0 // indirect + github.com/goccy/go-json v0.10.5 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/huandu/xstrings v1.4.0 // indirect + github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/magiconair/properties v1.8.9 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/minio/sha256-simd v1.0.0 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-multihash v0.2.3 // indirect - github.com/multiformats/go-varint v0.0.6 // indirect + github.com/multiformats/go-varint v0.0.7 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/arch v0.9.0 // indirect - golang.org/x/crypto v0.28.0 // indirect - golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect - golang.org/x/net v0.30.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.19.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + golang.org/x/arch v0.14.0 // indirect + golang.org/x/crypto v0.35.0 // indirect + golang.org/x/exp v0.0.0-20250228200357-dead58393ab7 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - lukechampine.com/blake3 v1.1.6 // indirect + lukechampine.com/blake3 v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 88d96af..5e1be3f 100644 --- a/go.sum +++ b/go.sum @@ -31,14 +31,14 @@ github.com/anacrolix/missinggo v1.3.0/go.mod h1:bqHm8cE8xr+15uVfMG3BFui/TxyB6//H github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ= github.com/anacrolix/missinggo/v2 v2.2.0/go.mod h1:o0jgJoYOyaoYQ4E2ZMISVa9c88BbUBVQQW4QeRkNCGY= github.com/anacrolix/missinggo/v2 v2.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA= -github.com/anacrolix/missinggo/v2 v2.7.4 h1:47h5OXoPV8JbA/ACA+FLwKdYbAinuDO8osc2Cu9xkxg= -github.com/anacrolix/missinggo/v2 v2.7.4/go.mod h1:vVO5FEziQm+NFmJesc7StpkquZk+WJFCaL0Wp//2sa0= +github.com/anacrolix/missinggo/v2 v2.8.0 h1:6pGnVOlR6TWL9JM5Msyezij8YHU3+oHO7r82Eql/kpA= +github.com/anacrolix/missinggo/v2 v2.8.0/go.mod h1:vVO5FEziQm+NFmJesc7StpkquZk+WJFCaL0Wp//2sa0= github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg= github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8= -github.com/anacrolix/torrent v1.57.1 h1:CS8rYfC2Oe15NPBhwCNs/3WBY6HiBCPDFpY+s9aFHbA= -github.com/anacrolix/torrent v1.57.1/go.mod h1:NNBg4lP2/us9Hp5+cLNcZRILM69cNoKIkqMGqr9AuR0= +github.com/anacrolix/torrent v1.58.1 h1:6FP+KH57b1gyT2CpVL9fEqf9MGJEgh3xw1VA8rI0pW8= +github.com/anacrolix/torrent v1.58.1/go.mod h1:/7ZdLuHNKgtCE1gjYJCfbtG9JodBcDaF5ip5EUWRtk8= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/autobrr/go-qbittorrent v1.11.0 h1:Xmt28ECvZYDiamabEUtDZvJxiO/NaoGjJF4mHESDXq4= github.com/autobrr/go-qbittorrent v1.11.0/go.mod h1:sIwIdqDcFbN67tSC5p5Lp27M8/BQFoSoW5XqXyVdHF0= @@ -52,27 +52,26 @@ github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2w github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8= github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og= -github.com/bytedance/sonic v1.12.2 h1:oaMFuRTpMHYLpCntGca65YWt5ny+wAceDERTkT2L9lg= -github.com/bytedance/sonic v1.12.2/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= +github.com/bytedance/sonic v1.12.9 h1:Od1BvK55NnewtGaJsTDeAOSnLVO2BTSLOe0+ooKokmQ= +github.com/bytedance/sonic v1.12.9/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= -github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0= +github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= -github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cytec/releaseparser v0.0.0-20200706155913-2341b265c370 h1:g9q5BGfDdhcXn4EmVZD8UydPXrvhSvgz3FRBn7zAJNs= github.com/cytec/releaseparser v0.0.0-20200706155913-2341b265c370/go.mod h1:T6MvuEQy8BSy+4Aol7AjqObp2g3wTv2HjcFrGToHVVE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= -github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= +github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -84,14 +83,14 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= -github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= -github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw= -github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E= -github.com/gin-contrib/requestid v1.0.3 h1:NB6SF0Te4Ikn8mW2K4tegpm2WGuB3bWj4wnWaM4oSAA= -github.com/gin-contrib/requestid v1.0.3/go.mod h1:VQd5IntEAH79uEt8FRoGpqiDWTKqjbRbSnmpNHXSsKs= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gin-contrib/cors v1.7.3 h1:hV+a5xp8hwJoTw7OY+a70FsL8JkVVFTXw9EcfrYUdns= +github.com/gin-contrib/cors v1.7.3/go.mod h1:M3bcKZhxzsvI+rlRSkkxHyljJt1ESd93COUvemZ79j4= +github.com/gin-contrib/requestid v1.0.4 h1:h9u+YSCMgrDcn2QlHn9c6P/Zwy4WdXqZLFTmlIAJWpA= +github.com/gin-contrib/requestid v1.0.4/go.mod h1:2/3cAmLKQ9E2Pr1IrSPR7K8AWiJORo0hLvs0keKsMJw= +github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E= +github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= @@ -110,11 +109,11 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= -github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= +github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= -github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -156,8 +155,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= -github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -168,10 +167,9 @@ github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVY github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= -github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -185,17 +183,18 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= -github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -215,8 +214,8 @@ github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= -github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY= -github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= +github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -249,18 +248,18 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= -github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= +github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -276,20 +275,21 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -298,8 +298,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= @@ -316,15 +317,15 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/arch v0.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k= -golang.org/x/arch v0.9.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4= +golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= -golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20250228200357-dead58393ab7 h1:aWwlzYV971S4BXRS9AmqwDLAD85ouC6X+pocatKY58c= +golang.org/x/exp v0.0.0-20250228200357-dead58393ab7/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -339,8 +340,8 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -348,8 +349,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -364,15 +365,14 @@ golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -394,8 +394,8 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -417,6 +417,6 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -lukechampine.com/blake3 v1.1.6 h1:H3cROdztr7RCfoaTpGZFQsrqvweFLrqS73j7L7cmR5c= -lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= +lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w= +lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/internal/api/token.go b/internal/api/token.go index 3978930..2a64e13 100644 --- a/internal/api/token.go +++ b/internal/api/token.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later package api diff --git a/internal/buildinfo/buildinfo.go b/internal/buildinfo/buildinfo.go index 0988fec..8e1cde2 100644 --- a/internal/buildinfo/buildinfo.go +++ b/internal/buildinfo/buildinfo.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later package buildinfo diff --git a/internal/domain/http.go b/internal/domain/http.go index 9fd3e54..d670adb 100644 --- a/internal/domain/http.go +++ b/internal/domain/http.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later package domain diff --git a/internal/domain/release.go b/internal/domain/release.go index 8c32983..943b871 100644 --- a/internal/domain/release.go +++ b/internal/domain/release.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later package domain diff --git a/internal/files/hardlink.go b/internal/files/hardlink.go new file mode 100644 index 0000000..8c6aa60 --- /dev/null +++ b/internal/files/hardlink.go @@ -0,0 +1,25 @@ +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. +// SPDX-License-Identifier: GPL-2.0-or-later + +package files + +import ( + "os" + "path/filepath" +) + +func CreateHardlink(sourcePath, targetPath string) error { + targetDirectory := filepath.Dir(targetPath) + + // create the target directory if it doesn't exist + if err := os.MkdirAll(targetDirectory, 0o755); err != nil { + return err + } + + // link source path to target path + if err := os.Link(sourcePath, targetPath); err != nil { + return err + } + + return nil +} diff --git a/internal/format/format.go b/internal/format/format.go new file mode 100644 index 0000000..89373e0 --- /dev/null +++ b/internal/format/format.go @@ -0,0 +1,45 @@ +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. +// SPDX-License-Identifier: GPL-2.0-or-later + +package format + +import ( + "fmt" + "regexp" + "strings" + + "github.com/moistari/rls" +) + +var ( + // regex for groups that don't need the folder name to be adjusted + ignoredRlsGrps = regexp.MustCompile(`(?i)^(ZR)$`) + + illegal = regexp.MustCompile(`(?i)[\\/:"*?<>|]`) + audio = regexp.MustCompile(`(?i)(AAC|DDP)\.(\d\.\d)`) + dots = regexp.MustCompile(`(?i)\.+`) +) + +func ComparableTitle(r rls.Release) string { + s := fmt.Sprintf("%s%d%d", rls.MustNormalize(r.Title), r.Year, r.Series) + + return s +} + +func CleanAnnounceTitle(release rls.Release) string { + packName := release.String() + + // check if RlsGrp of release is in ignore regex + if !ignoredRlsGrps.MatchString(release.Group) { + // remove illegal characters + packName = illegal.ReplaceAllString(packName, "") + // replace spaces with periods + packName = strings.ReplaceAll(packName, " ", ".") + // replace wrong audio naming + packName = audio.ReplaceAllString(packName, "$1$2") + // replace multiple dots with only one + packName = dots.ReplaceAllString(packName, ".") + } + + return packName +} diff --git a/internal/utils/formatting_test.go b/internal/format/format_test.go similarity index 92% rename from internal/utils/formatting_test.go rename to internal/format/format_test.go index 6251338..8bc605f 100644 --- a/internal/utils/formatting_test.go +++ b/internal/format/format_test.go @@ -1,7 +1,7 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later -package utils +package format import ( "testing" @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_GetFormattedTitle(t *testing.T) { +func Test_ComparableTitle(t *testing.T) { tests := []struct { name string packName string @@ -90,12 +90,12 @@ func Test_GetFormattedTitle(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := rls.ParseString(tt.packName) - assert.Equalf(t, tt.want, GetFormattedTitle(r), "FormatSeasonPackTitle(%s)", tt.packName) + assert.Equalf(t, tt.want, ComparableTitle(r), "ComparableTitle(%s)", tt.packName) }) } } -func Test_FormatSeasonPackTitle(t *testing.T) { +func Test_CleanAnnounceTitle(t *testing.T) { tests := []struct { name string packName string @@ -169,7 +169,8 @@ func Test_FormatSeasonPackTitle(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, FormatSeasonPackTitle(tt.packName), "FormatSeasonPackTitle(%s)", tt.packName) + r := rls.ParseString(tt.packName) + assert.Equalf(t, tt.want, CleanAnnounceTitle(r), "CleanAnnounceTitle(%s)", tt.packName) }) } } diff --git a/internal/http/processor.go b/internal/http/processor.go index 2b54f69..0f798fa 100644 --- a/internal/http/processor.go +++ b/internal/http/processor.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later package http @@ -12,10 +12,13 @@ import ( "github.com/nuxencs/seasonpackarr/internal/config" "github.com/nuxencs/seasonpackarr/internal/domain" + "github.com/nuxencs/seasonpackarr/internal/files" + "github.com/nuxencs/seasonpackarr/internal/format" "github.com/nuxencs/seasonpackarr/internal/logger" + "github.com/nuxencs/seasonpackarr/internal/metadata" "github.com/nuxencs/seasonpackarr/internal/release" + "github.com/nuxencs/seasonpackarr/internal/slices" "github.com/nuxencs/seasonpackarr/internal/torrents" - "github.com/nuxencs/seasonpackarr/internal/utils" "github.com/nuxencs/seasonpackarr/pkg/errors" "github.com/autobrr/go-qbittorrent" @@ -40,16 +43,15 @@ type request struct { } type entry struct { - t qbittorrent.Torrent - r rls.Release + torrent qbittorrent.Torrent + release rls.Release } -type torrentRlsEntries struct { +type entryCache struct { entriesMap map[string][]entry rlsMap map[string]rls.Release lastUpdated time.Time - err error - sync.Mutex + mu sync.Mutex } type matchInfo struct { @@ -59,9 +61,9 @@ type matchInfo struct { } var ( - clientMap = xsync.NewMapOf[string, *qbittorrent.Client]() - matchesMap = xsync.NewMapOf[string, []matchInfo]() - torrentMap = xsync.NewMapOf[string, *torrentRlsEntries]() + clientMap = xsync.NewMapOf[string, *qbittorrent.Client]() + matchMap = xsync.NewMapOf[string, []matchInfo]() + entryMap = xsync.NewMapOf[string, *entryCache]() ) func newProcessor(log logger.Logger, config *config.AppConfig, notification domain.Sender) *processor { @@ -94,39 +96,39 @@ func (p *processor) getClient(client *domain.Client, clientName string) error { return nil } -func (p *processor) getAllTorrents(clientName string) torrentRlsEntries { - f := func() *torrentRlsEntries { - tre, ok := torrentMap.Load(clientName) +func (p *processor) getAllTorrents(clientName string) (map[string][]entry, error) { + f := func() *entryCache { + tre, ok := entryMap.Load(clientName) if ok { return tre } - entries := &torrentRlsEntries{rlsMap: make(map[string]rls.Release)} - torrentMap.Store(clientName, entries) + entries := &entryCache{rlsMap: make(map[string]rls.Release)} + entryMap.Store(clientName, entries) return entries } entries := f() cur := time.Now() if entries.lastUpdated.After(cur) { - return *entries + return entries.entriesMap, nil } - entries.Lock() - defer entries.Unlock() + entries.mu.Lock() + defer entries.mu.Unlock() entries = f() if entries.lastUpdated.After(cur) { - return *entries + return entries.entriesMap, nil } ts, err := p.req.Client.GetTorrents(qbittorrent.TorrentFilterOptions{}) if err != nil { - return torrentRlsEntries{err: err} + return nil, errors.Wrap(err, "failed to get torrents") } after := time.Now() - entries = &torrentRlsEntries{entriesMap: make(map[string][]entry), lastUpdated: after.Add(after.Sub(cur)), rlsMap: entries.rlsMap} + entries = &entryCache{entriesMap: make(map[string][]entry), lastUpdated: after.Add(after.Sub(cur)), rlsMap: entries.rlsMap} for _, t := range ts { r, ok := entries.rlsMap[t.Name] @@ -135,16 +137,39 @@ func (p *processor) getAllTorrents(clientName string) torrentRlsEntries { entries.rlsMap[t.Name] = r } - fmtTitle := utils.GetFormattedTitle(r) - entries.entriesMap[fmtTitle] = append(entries.entriesMap[fmtTitle], entry{t: t, r: r}) + comparableTitle := format.ComparableTitle(r) + entries.entriesMap[comparableTitle] = append(entries.entriesMap[comparableTitle], entry{torrent: t, release: r}) } - torrentMap.Store(clientName, entries) - return *entries + entryMap.Store(clientName, entries) + return entries.entriesMap, nil } -func (p *processor) getFiles(hash string) (*qbittorrent.TorrentFiles, error) { - return p.req.Client.GetFilesInformation(hash) +func (p *processor) getFiles(hash string) (string, int64, error) { + torrentFiles, err := p.req.Client.GetFilesInformation(hash) + if err != nil { + return "", 0, err + } + + var fileName string + var size int64 + for _, f := range *torrentFiles { + if !release.IsValidEpisodeFile(f.Name) { + continue + } + + fileName = f.Name + size = f.Size + break + } + switch { + case len(fileName) == 0: + return "", 0, errors.New("file name is empty") + case size == 0: + return "", 0, errors.New("file size is empty") + } + + return fileName, size, nil } func (p *processor) getClientName() string { @@ -212,36 +237,36 @@ func (p *processor) processSeasonPack() (domain.StatusCode, error) { return c.Str("release", p.req.Name).Str("clientname", clientName) }) + if len(p.req.Name) == 0 { + return domain.StatusAnnounceNameError, domain.StatusAnnounceNameError.Error() + } + clientCfg, ok := p.cfg.Config.Clients[clientName] if !ok { return domain.StatusClientNotFound, domain.StatusClientNotFound.Error() } p.log.Info().Msgf("using %s client serving at %s:%d", clientName, clientCfg.Host, clientCfg.Port) - if len(p.req.Name) == 0 { - return domain.StatusAnnounceNameError, domain.StatusAnnounceNameError.Error() - } - if err := p.getClient(clientCfg, clientName); err != nil { return domain.StatusGetClientError, errors.Wrap(err, domain.StatusGetClientError.String()) } - tre := p.getAllTorrents(clientName) - if tre.err != nil { - return domain.StatusGetTorrentsError, errors.Wrap(tre.err, domain.StatusGetTorrentsError.String()) + entries, err := p.getAllTorrents(clientName) + if err != nil { + return domain.StatusGetTorrentsError, errors.Wrap(err, domain.StatusGetTorrentsError.String()) } requestRls := rls.ParseString(p.req.Name) - clientEntries, ok := tre.entriesMap[utils.GetFormattedTitle(requestRls)] + filteredEntries, ok := entries[format.ComparableTitle(requestRls)] if !ok { return domain.StatusNoMatches, domain.StatusNoMatches.Error() } - announcedPackName := utils.FormatSeasonPackTitle(p.req.Name) + announcedPackName := format.CleanAnnounceTitle(requestRls) p.log.Debug().Msgf("formatted season pack name: %s", announcedPackName) - for _, clientEntry := range clientEntries { - switch compareInfo := release.CheckCandidates(requestRls, clientEntry.r, p.cfg.Config.FuzzyMatching); compareInfo.StatusCode { + for _, filteredEntry := range filteredEntries { + switch compareInfo := release.CheckCandidates(requestRls, filteredEntry.release, p.cfg.Config.FuzzyMatching); compareInfo.StatusCode { case domain.StatusAlreadyInClient, domain.StatusNotASeasonPack: return compareInfo.StatusCode, compareInfo.StatusCode.Error() } @@ -249,10 +274,10 @@ func (p *processor) processSeasonPack() (domain.StatusCode, error) { codeSet := make(map[domain.StatusCode]bool) epsSet := make(map[int]struct{}) - matches := make([]matchInfo, 0, len(clientEntries)) + matches := make([]matchInfo, 0, len(filteredEntries)) - for _, clientEntry := range clientEntries { - switch compareInfo := release.CheckCandidates(requestRls, clientEntry.r, p.cfg.Config.FuzzyMatching); compareInfo.StatusCode { + for _, filteredEntry := range filteredEntries { + switch compareInfo := release.CheckCandidates(requestRls, filteredEntry.release, p.cfg.Config.FuzzyMatching); compareInfo.StatusCode { case domain.StatusAlreadyInClient, domain.StatusNotASeasonPack: return compareInfo.StatusCode, compareInfo.StatusCode.Error() @@ -260,39 +285,23 @@ func (p *processor) processSeasonPack() (domain.StatusCode, error) { domain.StatusCutMismatch, domain.StatusEditionMismatch, domain.StatusRepackStatusMismatch, domain.StatusHdrMismatch, domain.StatusStreamingServiceMismatch: p.log.Info().Msgf("%s: request(%s => %v), client(%s => %v)", - compareInfo.StatusCode, requestRls.String(), compareInfo.RejectValueA, - clientEntry.r.String(), compareInfo.RejectValueB) + compareInfo.StatusCode, requestRls, compareInfo.RejectValueA, + filteredEntry.release, compareInfo.RejectValueB) codeSet[compareInfo.StatusCode] = true continue case domain.StatusSuccessfulMatch: - torrentFiles, err := p.getFiles(clientEntry.t.Hash) + fileName, size, err := p.getFiles(filteredEntry.torrent.Hash) if err != nil { - p.log.Error().Err(err).Msgf("error getting files: %s", clientEntry.t.Name) + p.log.Error().Err(err).Msgf("error getting file info: %s", filteredEntry.torrent.Name) continue } - var fileName string - var size int64 - for _, f := range *torrentFiles { - if !release.IsValidEpisodeFile(f.Name) { - continue - } - - fileName = f.Name - size = f.Size - break - } - if len(fileName) == 0 || size == 0 { - p.log.Error().Err(err).Msgf("error getting filename or size: %s", clientEntry.t.Name) - continue - } - - epRls := rls.ParseString(clientEntry.t.Name) - clientEpPath := filepath.Join(clientEntry.t.SavePath, fileName) + clientEpPath := filepath.Join(filteredEntry.torrent.SavePath, fileName) announcedEpPath := filepath.Join(clientCfg.PreImportPath, announcedPackName, filepath.Base(fileName)) + episode := filteredEntry.release.Episode - epsSet[epRls.Episode] = struct{}{} + epsSet[episode] = struct{}{} // append current matchInfo to matches slice matches = append(matches, matchInfo{ @@ -302,7 +311,7 @@ func (p *processor) processSeasonPack() (domain.StatusCode, error) { }) p.log.Debug().Msgf("matched torrent from client: name(%s), size(%d), hash(%s)", - clientEntry.t.Name, size, clientEntry.t.Hash) + filteredEntry.torrent.Name, size, filteredEntry.torrent.Hash) codeSet[compareInfo.StatusCode] = true continue } @@ -312,12 +321,8 @@ func (p *processor) processSeasonPack() (domain.StatusCode, error) { return domain.StatusNoMatches, domain.StatusNoMatches.Error() } - // dedupe matches and store in matchesMap - matches = utils.DedupeSlice(matches) - matchesMap.Store(p.req.Name, matches) - if p.cfg.Config.SmartMode { - totalEps, err := utils.GetEpisodesPerSeason(requestRls.Title, requestRls.Series) + totalEps, err := metadata.EpisodesInSeason(requestRls) if err != nil { return domain.StatusEpisodeCountError, errors.Wrap(err, domain.StatusEpisodeCountError.String()) } @@ -326,14 +331,15 @@ func (p *processor) processSeasonPack() (domain.StatusCode, error) { percentEps := release.PercentOfTotalEpisodes(totalEps, foundEps) if percentEps < p.cfg.Config.SmartModeThreshold { - // delete match from matchesMap if threshold is not met - matchesMap.Delete(p.req.Name) - return domain.StatusBelowThreshold, errors.Wrap(fmt.Errorf("found %d/%d (%.2f%%) episodes in client", foundEps, totalEps, percentEps*100), domain.StatusBelowThreshold.String()) } } + // dedupe matches and store in matchMap + matches = slices.Dedupe(matches) + matchMap.Store(p.req.Name, matches) + if p.cfg.Config.ParseTorrentFile { return domain.StatusSuccessfulMatch, nil } @@ -341,7 +347,7 @@ func (p *processor) processSeasonPack() (domain.StatusCode, error) { successfulHardlink := false for _, match := range matches { - if err := utils.CreateHardlink(match.clientEpPath, match.announcedEpPath); err != nil { + if err := files.CreateHardlink(match.clientEpPath, match.announcedEpPath); err != nil { p.log.Error().Err(err).Msgf("error creating hardlink: %s", match.clientEpPath) continue } @@ -429,14 +435,14 @@ func (p *processor) parseTorrent() (domain.StatusCode, error) { } p.req.Torrent = torrentBytes - torrentInfo, err := torrents.ParseInfoFromTorrentBytes(p.req.Torrent) + torrentInfo, err := torrents.Info(p.req.Torrent) if err != nil { return domain.StatusParseTorrentInfoError, errors.Wrap(err, domain.StatusParseTorrentInfoError.String()) } parsedPackName := torrentInfo.BestName() p.log.Debug().Msgf("parsed season pack name: %s", parsedPackName) - torrentEps, err := torrents.GetEpisodesFromTorrentInfo(torrentInfo) + torrentEps, err := torrents.Episodes(torrentInfo) if err != nil { return domain.StatusGetEpisodesError, errors.Wrap(err, domain.StatusGetEpisodesError.String()) } @@ -444,7 +450,7 @@ func (p *processor) parseTorrent() (domain.StatusCode, error) { p.log.Debug().Msgf("found episode in pack: name(%s), size(%d)", torrentEp.Path, torrentEp.Size) } - matches, ok := matchesMap.Load(p.req.Name) + matches, ok := matchMap.Load(p.req.Name) if !ok { return domain.StatusNoMatches, domain.StatusNoMatches.Error() } @@ -471,7 +477,7 @@ func (p *processor) parseTorrent() (domain.StatusCode, error) { targetEpPath = filepath.Join(targetPackDir, matchedEpPath) successfulEpMatch = true - if err = utils.CreateHardlink(match.clientEpPath, targetEpPath); err != nil { + if err = files.CreateHardlink(match.clientEpPath, targetEpPath); err != nil { p.log.Error().Err(err).Msgf("error creating hardlink: %s", match.clientEpPath) continue } diff --git a/internal/http/webhook.go b/internal/http/webhook.go index a0114c4..042c28f 100644 --- a/internal/http/webhook.go +++ b/internal/http/webhook.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later package http diff --git a/internal/utils/tvmaze.go b/internal/metadata/tvmaze.go similarity index 64% rename from internal/utils/tvmaze.go rename to internal/metadata/tvmaze.go index ed513fa..f54ff20 100644 --- a/internal/utils/tvmaze.go +++ b/internal/metadata/tvmaze.go @@ -1,20 +1,21 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later -package utils +package metadata import ( "fmt" "github.com/nuxencs/seasonpackarr/pkg/errors" + "github.com/moistari/rls" "github.com/mrobinsn/go-tvmaze/tvmaze" ) -func GetEpisodesPerSeason(title string, season int) (int, error) { +func EpisodesInSeason(release rls.Release) (int, error) { totalEpisodes := 0 - show, err := tvmaze.DefaultClient.GetShow(normalizeTitle(title)) + show, err := tvmaze.DefaultClient.GetShow(rls.MustNormalize(release.Title)) if err != nil { return 0, errors.Wrap(err, "failed to find show on tvmaze") } @@ -25,13 +26,13 @@ func GetEpisodesPerSeason(title string, season int) (int, error) { } for _, episode := range episodes { - if episode.Season == season { + if episode.Season == release.Series { totalEpisodes++ } } if totalEpisodes == 0 { - return 0, fmt.Errorf("failed to find episodes in season %d of %q", season, title) + return 0, fmt.Errorf("failed to find episodes in season %d of %q", release.Series, release.Title) } return totalEpisodes, nil diff --git a/internal/metadata/tvmaze_test.go b/internal/metadata/tvmaze_test.go new file mode 100644 index 0000000..0c5ff6d --- /dev/null +++ b/internal/metadata/tvmaze_test.go @@ -0,0 +1,86 @@ +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. +// SPDX-License-Identifier: GPL-2.0-or-later + +package metadata + +import ( + "testing" + + "github.com/moistari/rls" + "github.com/stretchr/testify/assert" +) + +func Test_EpisodesInSeason(t *testing.T) { + tests := []struct { + name string + release rls.Release + want int + wantErr bool + }{ + { + name: "some_show", + release: rls.Release{ + Title: "Halo", + Series: 1, + }, + want: 9, + wantErr: false, + }, + { + name: "anime_show", + release: rls.Release{ + Title: "Attack on Titan", + Series: 1, + }, + want: 25, + wantErr: false, + }, + { + name: "season_doesnt_exist", + release: rls.Release{ + Title: "Halo", + Series: 0, + }, + want: 0, + wantErr: true, + }, + { + name: "show_doesnt_exist", + release: rls.Release{ + Title: "Test123", + Series: 0, + }, + want: 0, + wantErr: true, + }, + { + name: "some_recent_show", + release: rls.Release{ + Title: "Echo", + Series: 1, + }, + want: 5, + wantErr: false, + }, + { + name: "show_with_punctuation", + release: rls.Release{ + Title: "Orphan Black - Echoes", + Series: 1, + }, + want: 10, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := EpisodesInSeason(tt.release) + + if (err != nil) != tt.wantErr { + t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equalf(t, tt.want, got, "EpisodesInSeason(%s, %d)", tt.release.Title, tt.release.Series) + }) + } +} diff --git a/internal/payload/payload.go b/internal/payload/payload.go index ab59def..36cdaa3 100644 --- a/internal/payload/payload.go +++ b/internal/payload/payload.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later package payload @@ -36,7 +36,7 @@ type parseVars struct { ClientName string } -func CompilePackPayload(torrentName string, clientName string) (io.Reader, error) { +func CompilePack(torrentName string, clientName string) (io.Reader, error) { var buffer bytes.Buffer tmplVars := packVars{ @@ -56,7 +56,7 @@ func CompilePackPayload(torrentName string, clientName string) (io.Reader, error return &buffer, nil } -func CompileParsePayload(torrentName string, torrentBytes []byte, clientName string) (io.Reader, error) { +func CompileParse(torrentName string, torrentBytes []byte, clientName string) (io.Reader, error) { var buffer bytes.Buffer tmplVars := parseVars{ @@ -77,7 +77,7 @@ func CompileParsePayload(torrentName string, torrentBytes []byte, clientName str return &buffer, nil } -func ExecRequest(url string, body io.Reader, apiToken string) error { +func Exec(url string, body io.Reader, apiToken string) error { req, err := http.NewRequest(http.MethodPost, url, body) if err != nil { return err diff --git a/internal/release/release.go b/internal/release/release.go index 4ec737d..8b323b5 100644 --- a/internal/release/release.go +++ b/internal/release/release.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later package release @@ -7,7 +7,7 @@ import ( "path/filepath" "github.com/nuxencs/seasonpackarr/internal/domain" - "github.com/nuxencs/seasonpackarr/internal/utils" + "github.com/nuxencs/seasonpackarr/internal/slices" "github.com/moistari/rls" ) @@ -19,11 +19,11 @@ func CheckCandidates(requestRls, clientRls rls.Release, fuzzyMatching domain.Fuz return domain.CompareInfo{StatusCode: domain.StatusNotASeasonPack} } - return compareReleases(requestRls, clientRls, fuzzyMatching) + return compare(requestRls, clientRls, fuzzyMatching) } -func compareReleases(requestRls, clientRls rls.Release, fuzzyMatching domain.FuzzyMatching) domain.CompareInfo { - if rls.MustNormalize(requestRls.Resolution) != rls.MustNormalize(clientRls.Resolution) { +func compare(requestRls, clientRls rls.Release, fuzzyMatching domain.FuzzyMatching) domain.CompareInfo { + if requestRls.Resolution != clientRls.Resolution { return domain.CompareInfo{ StatusCode: domain.StatusResolutionMismatch, RejectValueA: requestRls.Resolution, @@ -31,7 +31,7 @@ func compareReleases(requestRls, clientRls rls.Release, fuzzyMatching domain.Fuz } } - if rls.MustNormalize(requestRls.Source) != rls.MustNormalize(clientRls.Source) { + if requestRls.Source != clientRls.Source { return domain.CompareInfo{ StatusCode: domain.StatusSourceMismatch, RejectValueA: requestRls.Source, @@ -47,7 +47,7 @@ func compareReleases(requestRls, clientRls rls.Release, fuzzyMatching domain.Fuz } } - if !utils.EqualElements(requestRls.Cut, clientRls.Cut) { + if !slices.EqualElements(requestRls.Cut, clientRls.Cut) { return domain.CompareInfo{ StatusCode: domain.StatusCutMismatch, RejectValueA: requestRls.Cut, @@ -55,7 +55,7 @@ func compareReleases(requestRls, clientRls rls.Release, fuzzyMatching domain.Fuz } } - if !utils.EqualElements(requestRls.Edition, clientRls.Edition) { + if !slices.EqualElements(requestRls.Edition, clientRls.Edition) { return domain.CompareInfo{ StatusCode: domain.StatusEditionMismatch, RejectValueA: requestRls.Edition, @@ -65,7 +65,7 @@ func compareReleases(requestRls, clientRls rls.Release, fuzzyMatching domain.Fuz // skip comparing repack status when skipRepackCompare is enabled if !fuzzyMatching.SkipRepackCompare { - if !utils.EqualElements(requestRls.Other, clientRls.Other) { + if !slices.EqualElements(requestRls.Other, clientRls.Other) { return domain.CompareInfo{ StatusCode: domain.StatusRepackStatusMismatch, RejectValueA: requestRls.Other, @@ -76,11 +76,11 @@ func compareReleases(requestRls, clientRls rls.Release, fuzzyMatching domain.Fuz // normalize any HDR format down to plain HDR when simplifyHdrCompare is enabled if fuzzyMatching.SimplifyHdrCompare { - requestRls.HDR = utils.SimplifyHDRSlice(requestRls.HDR) - clientRls.HDR = utils.SimplifyHDRSlice(clientRls.HDR) + requestRls.HDR = slices.SimplifyHDR(requestRls.HDR) + clientRls.HDR = slices.SimplifyHDR(clientRls.HDR) } - if !utils.EqualElements(requestRls.HDR, clientRls.HDR) { + if !slices.EqualElements(requestRls.HDR, clientRls.HDR) { return domain.CompareInfo{ StatusCode: domain.StatusHdrMismatch, RejectValueA: requestRls.HDR, diff --git a/internal/utils/slices.go b/internal/slices/slices.go similarity index 72% rename from internal/utils/slices.go rename to internal/slices/slices.go index 5e90f86..90a4ccd 100644 --- a/internal/utils/slices.go +++ b/internal/slices/slices.go @@ -1,21 +1,21 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later -package utils +package slices import ( "strings" ) -func DedupeSlice[T comparable](s []T) []T { +func Dedupe[T comparable](s []T) []T { resultSet := make(map[T]struct{}) for _, i := range s { resultSet[i] = struct{}{} } result := make([]T, 0, len(resultSet)) - for str := range resultSet { - result = append(result, str) + for item := range resultSet { + result = append(result, item) } return result @@ -41,7 +41,7 @@ func EqualElements[T comparable](x, y []T) bool { return true } -func SimplifyHDRSlice(hdrSlice []string) []string { +func SimplifyHDR(hdrSlice []string) []string { for i := range hdrSlice { if strings.Contains(hdrSlice[i], "HDR") { hdrSlice[i] = "HDR" diff --git a/internal/utils/slices_test.go b/internal/slices/slices_test.go similarity index 90% rename from internal/utils/slices_test.go rename to internal/slices/slices_test.go index c24650d..bc8643d 100644 --- a/internal/utils/slices_test.go +++ b/internal/slices/slices_test.go @@ -1,7 +1,7 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later -package utils +package slices import ( "testing" @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_DedupeSlice(t *testing.T) { +func Test_Dedupe(t *testing.T) { tests := []struct { name string slice interface{} @@ -60,9 +60,9 @@ func Test_DedupeSlice(t *testing.T) { t.Run(tt.name, func(t *testing.T) { switch v := tt.slice.(type) { case []string: - assert.ElementsMatchf(t, tt.want, DedupeSlice(v), "Dedupe(%v)", v) + assert.ElementsMatchf(t, tt.want, Dedupe(v), "Dedupe(%v)", v) case []int: - assert.ElementsMatchf(t, tt.want, DedupeSlice(v), "Dedupe(%v)", v) + assert.ElementsMatchf(t, tt.want, Dedupe(v), "Dedupe(%v)", v) default: t.Errorf("Unsupported slice type in test case: %v", tt.name) } @@ -154,7 +154,7 @@ func Test_EqualElements(t *testing.T) { } } -func Test_SimplifyHDRSlice(t *testing.T) { +func Test_SimplifyHDR(t *testing.T) { tests := []struct { name string input []string @@ -183,7 +183,7 @@ func Test_SimplifyHDRSlice(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, SimplifyHDRSlice(tt.input), "SimplifyHDRSlice(%v)", tt.input) + assert.Equalf(t, tt.want, SimplifyHDR(tt.input), "SimplifyHDR(%v)", tt.input) }) } } diff --git a/internal/torrents/decode.go b/internal/torrents/decode.go index eef704b..77c9cfa 100644 --- a/internal/torrents/decode.go +++ b/internal/torrents/decode.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 - 2024, KyleSanderson, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, KyleSanderson, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later package torrents diff --git a/internal/torrents/torrents.go b/internal/torrents/torrents.go index 7e1228b..d2bd514 100644 --- a/internal/torrents/torrents.go +++ b/internal/torrents/torrents.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later package torrents @@ -18,8 +18,8 @@ type Episode struct { Size int64 } -func ParseInfoFromTorrentBytes(torrentBytes []byte) (metainfo.Info, error) { - metaInfo, err := metainfo.Load(bytes.NewReader(torrentBytes)) +func Info(torrent []byte) (metainfo.Info, error) { + metaInfo, err := metainfo.Load(bytes.NewReader(torrent)) if err != nil { return metainfo.Info{}, err } @@ -27,7 +27,7 @@ func ParseInfoFromTorrentBytes(torrentBytes []byte) (metainfo.Info, error) { return metaInfo.UnmarshalInfo() } -func GetEpisodesFromTorrentInfo(info metainfo.Info) ([]Episode, error) { +func Episodes(info metainfo.Info) ([]Episode, error) { if !info.IsDir() { return []Episode{}, fmt.Errorf("not a directory") } diff --git a/internal/utils/formatting.go b/internal/utils/formatting.go deleted file mode 100644 index 952e31b..0000000 --- a/internal/utils/formatting.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. -// SPDX-License-Identifier: GPL-2.0-or-later - -package utils - -import ( - "fmt" - "regexp" - "strings" - - "github.com/moistari/rls" -) - -func GetFormattedTitle(r rls.Release) string { - s := fmt.Sprintf("%s%d%d", rls.MustNormalize(r.Title), r.Year, r.Series) - - return s -} - -func FormatSeasonPackTitle(packName string) string { - // regex for groups that don't need the folder name to be adjusted - reIgnoredRlsGrps := regexp.MustCompile(`(?i)^(ZR)$`) - - reIllegal := regexp.MustCompile(`(?i)[\\/:"*?<>|]`) - reAudio := regexp.MustCompile(`(?i)(AAC|DDP)\.(\d\.\d)`) - reDots := regexp.MustCompile(`(?i)\.+`) - - r := rls.ParseString(packName) - - // check if RlsGrp of release is in ignore regex - if !reIgnoredRlsGrps.MatchString(r.Group) { - // remove illegal characters - packName = reIllegal.ReplaceAllString(packName, "") - // replace spaces with periods - packName = strings.ReplaceAll(packName, " ", ".") - // replace wrong audio naming - packName = reAudio.ReplaceAllString(packName, "$1$2") - // replace multiple dots with only one - packName = reDots.ReplaceAllString(packName, ".") - } - return packName -} - -func normalizeTitle(title string) string { - return rls.MustNormalize(title) -} diff --git a/internal/utils/hardlink.go b/internal/utils/hardlink.go deleted file mode 100644 index fe2e36f..0000000 --- a/internal/utils/hardlink.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. -// SPDX-License-Identifier: GPL-2.0-or-later - -package utils - -import ( - "os" - "path/filepath" -) - -func CreateHardlink(srcPath, trgPath string) error { - trgDir := filepath.Dir(trgPath) - - // create the target directory if it doesn't exist - if err := os.MkdirAll(trgDir, 0o755); err != nil { - return err - } - - // link source path to target path if it doesn't exist - if err := os.Link(srcPath, trgPath); err != nil { - return err - } - - return nil -} diff --git a/internal/utils/tvmaze_test.go b/internal/utils/tvmaze_test.go deleted file mode 100644 index 7b3bdd1..0000000 --- a/internal/utils/tvmaze_test.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. -// SPDX-License-Identifier: GPL-2.0-or-later - -package utils - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_GetEpisodesPerSeason(t *testing.T) { - tests := []struct { - name string - title string - season int - want int - wantErr bool - }{ - { - name: "some_show", - title: "Halo", - season: 1, - want: 9, - wantErr: false, - }, - { - name: "anime_show", - title: "Attack on Titan", - season: 1, - want: 25, - wantErr: false, - }, - { - name: "season_doesnt_exist", - title: "Halo", - season: 0, - want: 0, - wantErr: true, - }, - { - name: "show_doesnt_exist", - title: "Test123", - season: 0, - want: 0, - wantErr: true, - }, - { - name: "some_recent_show", - title: "Echo", - season: 1, - want: 5, - wantErr: false, - }, - { - name: "show_with_punctuation", - title: "Orphan Black - Echoes", - season: 1, - want: 10, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := GetEpisodesPerSeason(tt.title, tt.season) - - if (err != nil) != tt.wantErr { - t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) - return - } - assert.Equalf(t, tt.want, got, "GetEpisodesPerSeason(%s, %d)", tt.title, tt.season) - }) - } -} diff --git a/main.go b/main.go index 92d3e0d..8513cf6 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 - 2024, nuxen and the seasonpackarr contributors. +// Copyright (c) 2023 - 2025, nuxen and the seasonpackarr contributors. // SPDX-License-Identifier: GPL-2.0-or-later package main