From 8180aef994d7b834f476a6073c01120cae514cb9 Mon Sep 17 00:00:00 2001 From: ze0s <43699394+zze0s@users.noreply.github.com> Date: Wed, 13 Nov 2024 18:32:07 +0100 Subject: [PATCH] refactor: edit command and docs (#6) * refactor: edit command and docs * fix: export path * feat(edit): add progress bar output * chore: update go version and deps * build: update actions workflow * build: update actions workflow * build: merge workflows * chore: go mod tidy * chore: add test * chore: update go patch version * chore: add test * chore: add .gitignore --- .github/workflows/ci.yml | 56 ----------- .github/workflows/release.yml | 35 ++++--- .gitignore | 3 + .goreleaser.yml | 8 -- README.md | 18 +++- cmd/bencode.go | 178 ++++++++++++++++++++++++++-------- cmd/bencode_test.go | 24 +++++ go.mod | 11 ++- go.sum | 31 +++++- internal/bencoding/bencode.go | 133 ++++++++++++++----------- main.go | 2 +- 11 files changed, 319 insertions(+), 180 deletions(-) delete mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 cmd/bencode_test.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 7dcf2d5..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: ci - -on: - pull_request: - -permissions: - contents: write - packages: write - -jobs: - test: - name: Test - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: '1.20.1' - cache: true - - - name: Test - run: go test -v ./... - - - goreleaser: - name: Build binaries - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: '1.20.1' - cache: true - - - name: Run GoReleaser build and publish tags - uses: goreleaser/goreleaser-action@v3 - with: - distribution: goreleaser - version: latest - args: release --clean --skip-publish - - - name: Upload artifacts - uses: actions/upload-artifact@v3 - with: - name: migraterr - path: dist/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 953bd7a..8f255a5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,8 +2,11 @@ name: release on: push: + branches: + - "master" tags: - 'v*' + pull_request: permissions: contents: write @@ -15,38 +18,48 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: - go-version: '1.20.1' + go-version: '1.22.3' cache: true - name: Test run: go test -v ./... - goreleaser: name: Build & publish binaries - if: startsWith(github.ref, 'refs/tags/') + needs: [test] runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: - go-version: '1.20.1' + go-version: '1.22.3' cache: true + - name: Run GoReleaser build + if: github.event_name == 'pull_request' + uses: goreleaser/goreleaser-action@v5 + with: + distribution: goreleaser + version: latest + args: release --clean --skip=validate --skip=publish --parallelism 5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Run GoReleaser build and publish tags - uses: goreleaser/goreleaser-action@v3 + if: startsWith(github.ref, 'refs/tags/') + uses: goreleaser/goreleaser-action@v5 with: distribution: goreleaser version: latest @@ -54,8 +67,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Upload assets - uses: actions/upload-artifact@v3 + - name: Upload artifacts + uses: actions/upload-artifact@v4 with: name: migraterr path: dist/* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..120e2a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +test/* +bin \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml index b7b2918..de7a26f 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -35,14 +35,6 @@ archives: - id: migraterr builds: - migraterr - files: - - none* - name_template: >- - {{ .ProjectName }}_ - {{- .Version }}_ - {{- .Os }}_ - {{- if eq .Arch "amd64" }}x86_64 - {{- else }}{{ .Arch }}{{ end }} release: prerelease: auto diff --git a/README.md b/README.md index d265c5f..c445368 100644 --- a/README.md +++ b/README.md @@ -18,12 +18,24 @@ Available sub commands: #### edit +Usage: +```shell +migraterr bencode edit "~/.sessions/*.torrent.rtorrent" --replace "/old/path/|/new/path/" --dry-run --verbose +migraterr bencode edit "~/.sessions/*.torrent.rtorrent" --replace "/old/path/|/new/path/" --export "./edited-files" -v +``` + Required flags: -- `--glob` Glob to file(s) like `~/.sessions/*.torrent.rtorrent` -- `--replacements` Array of `oldvalue|newvalue` similar to `sed`. +- `--replace` Array of `oldvalue|newvalue` similar to `sed`. Optional flags: - `--dry-run` Do not edit any data - `--verbose` Verbose output - `--export /new/dir` Export edited files to new directory: -- `--export /new/dir` Export edited files to new directory: + +#### info + +Usage: +```shell +migraterr bencode info "~/.sessions/*.torrent.rtorrent" +migraterr bencode edit "~/.sessions/123.torrent.rtorrent" +``` diff --git a/cmd/bencode.go b/cmd/bencode.go index 5602f69..e3554e8 100644 --- a/cmd/bencode.go +++ b/cmd/bencode.go @@ -3,15 +3,18 @@ package cmd import ( "fmt" "log" + "os" "path/filepath" + "strings" "migraterr/internal/bencoding" + "github.com/pkg/errors" + "github.com/schollz/progressbar/v3" "github.com/spf13/cobra" ) -func RunBencode() *cobra.Command { - +func BencodeCommand() *cobra.Command { command := &cobra.Command{ Use: "bencode", Short: "bencoded tools", @@ -25,101 +28,198 @@ func RunBencode() *cobra.Command { return cmd.Usage() } - command.AddCommand(RunBencodeEdit()) - command.AddCommand(RunBencodeInfo()) + command.AddCommand(BencodeEditCommand()) + command.AddCommand(BencodeInfoCommand()) return command } -func RunBencodeEdit() *cobra.Command { - +func BencodeEditCommand() *cobra.Command { command := &cobra.Command{ Use: "edit", Short: "Edit bencode files", Long: `Edit bencoded files like .torrents and fastresume`, - Example: ` migraterr bencode edit + Example: ` migraterr bencode edit ./files/**/*.fastresume --replace "/old/path/|/new/path" migraterr bencode edit --help`, - SilenceUsage: true, + Args: cobra.MinimumNArgs(1), } var ( - dry bool - verbose bool - exportDir string - glob string - replacements []string + dry bool + verbose bool + exportDir string + replaceStrings []string + replacements []bencoding.Replacement ) command.Flags().BoolVar(&dry, "dry-run", false, "Dry run, don't write changes") command.Flags().BoolVarP(&verbose, "verbose", "v", false, "Verbose output") - command.Flags().StringVar(&glob, "glob", "", "Glob to files: eg ./files/**/*.torrent.rtorrent") command.Flags().StringVar(&exportDir, "export", "", "Export to directory. Will replace if not specified") - command.Flags().StringSliceVar(&replacements, "replace", []string{}, "Replace: pattern|replace") + command.Flags().StringSliceVar(&replaceStrings, "replace", []string{}, "Replace: pattern|replace") - command.Run = func(cmd *cobra.Command, args []string) { - if glob == "" { - log.Fatal("must have dir\n") + command.MarkFlagRequired("replace") + + command.RunE = func(cmd *cobra.Command, args []string) error { + path := args[0] + + if strings.HasPrefix(path, "~/") { + home, _ := os.UserHomeDir() + path = filepath.Join(home, path[2:]) } - if len(replacements) == 0 { + if len(replaceStrings) == 0 { log.Fatal("must supply replacements\n") } - processedFiles := 0 + // validate replaceStrings + if len(replaceStrings) > 0 { + for _, replacement := range replaceStrings { + err := validateReplacement(replacement) + if err != nil { + return err + } + + parts := strings.Split(replacement, "|") + if len(parts) > 2 { + return errors.Errorf("invalid replacement: %q - contains multiple |", replacement) + } + + replacements = append(replacements, bencoding.Replacement{ + Old: parts[0], + New: parts[1], + }) + } + } - files, err := filepath.Glob(glob) + files, err := filepath.Glob(path) if err != nil { - log.Fatal("could not open files\n") + return err } - for _, file := range files { + if len(files) == 0 { + return errors.Errorf("no files found matching pattern: %q", path) + } + + if exportDir != "" { + err = os.MkdirAll(exportDir, os.ModePerm) + if err != nil { + return errors.Wrapf(err, "could not create export dir: %q", exportDir) + } + } + + if verbose { + log.Printf("Found %d files\n", len(files)) + } + + processedFiles := 0 + modifiedFiles := 0 + + bar := progressbar.NewOptions(len(files), progressbar.OptionSetDescription("Processing files...")) + + for idx, file := range files { _, fileName := filepath.Split(file) - if err := bencoding.Process(file, exportDir, replacements, verbose, dry); err != nil { - log.Fatalf("error processing file: %q\n", err) + if !verbose { + bar.Add(1) + } + + exportPath := file + if exportDir != "" { + exportPath = filepath.Join(exportDir, fileName) + } + + if dry { + if verbose { + log.Printf("[%d/%d] dry-run: process file %s\n", idx, len(files), fileName) + } + } else { + if verbose { + log.Printf("[%d/%d] process file %s\n", idx, len(files), fileName) + } + } + + currentIteration := fmt.Sprintf("[%d/%d]", idx, len(files)) + + modified, err := openAndProcessFile(file, exportPath, replacements, verbose, dry, currentIteration) + if err != nil { + return errors.Wrapf(err, "could not open and process file: %q", file) + } + + if modified { + modifiedFiles++ } processedFiles++ - if verbose { - log.Printf("[%d/%d] sucessfully processed file %s\n", len(files), processedFiles, fileName) + if dry { + if verbose { + log.Printf("[%d/%d] dry-run: sucessfully processed file %s\n", processedFiles, len(files), fileName) + } + } else { + if verbose { + log.Printf("[%d/%d] sucessfully processed file %s\n", processedFiles, len(files), fileName) + } } + } + if verbose { + log.Printf("migraterr bencode processed %d files and modified %d files\n", processedFiles, modifiedFiles) } - log.Printf("migraterr bencode processed %d files\n", processedFiles) + return nil } return command } -func RunBencodeInfo() *cobra.Command { +func validateReplacement(replacement string) error { + if !strings.Contains(replacement, "|") { + return errors.Errorf("invalid replacement: %q", replacement) + } + return nil +} + +func openAndProcessFile(filePath string, exportPath string, replacements []bencoding.Replacement, verbose bool, dry bool, currentIteration string) (bool, error) { + fileReader, err := os.Open(filePath) + if err != nil { + return false, errors.Wrapf(err, "could not read filePath: %s", filePath) + } + + defer fileReader.Close() + modified, err := bencoding.ProcessFile(fileReader, filePath, exportPath, replacements, verbose, dry, currentIteration) + if err != nil { + return false, errors.Wrapf(err, "error processing filePath: %s", filePath) + } + + return modified, nil +} + +func BencodeInfoCommand() *cobra.Command { command := &cobra.Command{ Use: "info", Short: "Info bencode files", Long: `Info bencoded files like .torrents and fastresume`, - Example: ` migraterr bencode info + Example: ` migraterr bencode info ./files/**/*.fastresume migraterr bencode info --help`, - SilenceUsage: true, + Args: cobra.MinimumNArgs(1), } - var glob string - - command.Flags().StringVar(&glob, "glob", "", "Glob to files: eg ./files/**/*.torrent.rtorrent") - command.Run = func(cmd *cobra.Command, args []string) { - if glob == "" { - log.Fatal("must have dir\n") + path := args[0] + + if strings.HasPrefix(path, "~/") { + home, _ := os.UserHomeDir() + path = filepath.Join(home, path[2:]) } - files, err := filepath.Glob(glob) + files, err := filepath.Glob(path) if err != nil { log.Fatal("could not open files\n") } - fmt.Printf("found %d files\n", len(files)) + fmt.Printf("Found %d files matching %q\n", len(files), path) for _, file := range files { if err := bencoding.Info(file); err != nil { diff --git a/cmd/bencode_test.go b/cmd/bencode_test.go new file mode 100644 index 0000000..f52a1cf --- /dev/null +++ b/cmd/bencode_test.go @@ -0,0 +1,24 @@ +package cmd + +import "testing" + +func Test_validateReplacement(t *testing.T) { + type args struct { + replacement string + } + tests := []struct { + name string + args args + wantErr bool + }{ + {name: "test", args: args{replacement: "/path/|/new/path/"}, wantErr: false}, + {name: "fail", args: args{replacement: "/path//new/path/"}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := validateReplacement(tt.args.replacement); (err != nil) != tt.wantErr { + t.Errorf("validateReplacement() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/go.mod b/go.mod index a3574a4..b4cac5e 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,19 @@ module migraterr -go 1.20 +go 1.22.3 require ( github.com/pkg/errors v0.9.1 - github.com/spf13/cobra v1.6.1 + github.com/schollz/progressbar/v3 v3.14.3 + github.com/spf13/cobra v1.8.0 github.com/zeebo/bencode v1.0.0 ) require ( - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect ) diff --git a/go.sum b/go.sum index 4b0dde9..f920dbc 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,35 @@ -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/schollz/progressbar/v3 v3.14.3 h1:oOuWW19ka12wxYU1XblR4n16wF/2Y1dBLMarMo6p4xU= +github.com/schollz/progressbar/v3 v3.14.3/go.mod h1:aT3UQ7yGm+2ZjeXPqsjTenwL3ddUiuZ0kfQ/2tHlyNI= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/zeebo/bencode v1.0.0 h1:zgop0Wu1nu4IexAZeCZ5qbsjU4O1vMrfCrVgUjbHVuA= github.com/zeebo/bencode v1.0.0/go.mod h1:Ct7CkrWIQuLWAy9M3atFHYq4kG9Ao/SsY5cdtCXmp9Y= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/bencoding/bencode.go b/internal/bencoding/bencode.go index c93d5c6..e892e63 100644 --- a/internal/bencoding/bencode.go +++ b/internal/bencoding/bencode.go @@ -3,6 +3,7 @@ package bencoding import ( "bufio" "fmt" + "io" "log" "os" "path/filepath" @@ -12,78 +13,84 @@ import ( "github.com/zeebo/bencode" ) -type BencodeFile map[string]interface{} - -func Process(filePath, exportDir string, replacements []string, verbose, dry bool) error { - file, err := os.ReadFile(filePath) - if err != nil { - log.Printf("error reading file: %s err: %q\n", filePath, err) - return errors.Wrapf(err, "could not read file: %s", filePath) - } - - if verbose { - log.Printf("reading file %s\n", filePath) - } +type Replacement struct { + Old string + New string +} - exportPath := filePath - if exportDir != "" { - _, file := filepath.Split(filePath) - exportPath = filepath.Join(exportDir, file) - } +type BencodeFile map[string]any +func ProcessFile(reader io.Reader, filePath, exportPath string, replacements []Replacement, verbose, dry bool, currentIteration string) (bool, error) { if dry { if verbose { - log.Printf("dry-run: process file %s\n", filePath) - log.Printf("dry-run: encode and write file %s\n", exportPath) - } - } else { - var decodedFile BencodeFile - if err := bencode.DecodeBytes(file, &decodedFile); err != nil { - log.Printf("could not decode bencode file: %s\n", filePath) - return errors.Wrapf(err, "could not decode file: %s", filePath) + log.Printf("%s dry-run: process file %s\n", currentIteration, filePath) } + } - if len(replacements) > 0 { - for k, v := range decodedFile { - for _, replacement := range replacements { - if !strings.Contains(replacement, "|") { - continue - } + var decodedFile BencodeFile + decoder := bencode.NewDecoder(reader) - parts := strings.Split(replacement, "|") + err := decoder.Decode(&decodedFile) + if err != nil { + if errors.Is(err, io.EOF) { + return false, nil + } + return false, errors.Wrapf(err, "could not decode file: %s", filePath) + } - if len(parts) == 0 || len(parts) > 2 { - continue - } + modified := false - switch val := v.(type) { - case string: - if strings.Contains(val, parts[0]) { - decodedFile[k] = strings.Replace(val, parts[0], parts[1], -1) + if len(replacements) > 0 { + for k, v := range decodedFile { + for _, replacement := range replacements { + + switch val := v.(type) { + case string: + if strings.Contains(val, replacement.Old) { + if dry { + if verbose { + log.Printf("%s dry-run: replaced: %q with %q\n", currentIteration, replacement.Old, replacement.New) + } + } else { + decodedFile[k] = strings.Replace(val, replacement.Old, replacement.New, -1) if verbose { - log.Printf("replaced: '%s' with '%s'\n", parts[0], parts[1]) + log.Printf("%s replaced: %q with %q\n", currentIteration, replacement.Old, replacement.New) } } - default: - continue - } + modified = true + } + default: + continue } } } + } - if err := decodedFile.EncodeAndWrite(exportPath); err != nil { - log.Printf("could not write fastresume file %s error: %q\n", exportPath, err) - return err - } + if !modified { + log.Printf("%s found no data to replace\n", currentIteration) } - if verbose { - log.Printf("sucessfully processed file %s\n", exportPath) + if dry { + if verbose { + log.Printf("%s dry-run: encode and write file %s\n", currentIteration, exportPath) + } + } else { + exportFile, err := os.Create(exportPath) + if err != nil { + return modified, errors.Wrapf(err, "could not create file: %s", exportPath) + } + + defer exportFile.Close() + + err = decodedFile.EncodeAndWriteFile(exportFile) + if err != nil { + return modified, errors.Wrapf(err, "could not write fastresume file %s", exportPath) + } } - return nil + return modified, nil } func Encode(path string, data any) error { @@ -109,6 +116,20 @@ func Encode(path string, data any) error { return nil } +func (f BencodeFile) EncodeAndWriteFile(file *os.File) error { + bufferedWriter := bufio.NewWriter(file) + encoder := bencode.NewEncoder(bufferedWriter) + if err := encoder.Encode(f); err != nil { + return errors.Wrapf(err, "could not encode file: %s", file.Name()) + } + + if err := bufferedWriter.Flush(); err != nil { + return errors.Wrapf(err, "could not flush buffered writer: %s", file.Name()) + } + + return nil +} + func (f BencodeFile) EncodeAndWrite(path string) error { dir := filepath.Dir(path) err := os.MkdirAll(dir, os.ModePerm) @@ -145,18 +166,22 @@ func Info(path string) error { log.Fatalf("error reading file: %s err: %q\n", path, err) } - var fastResume map[string]interface{} + var fastResume map[string]any if err := bencode.DecodeString(string(read), &fastResume); err != nil { log.Printf("could not decode bencode file %s\n", path) } _, fileName := filepath.Split(path) - fmt.Printf("\nFilename: %s\n", fileName) + var builder strings.Builder + + fmt.Fprintf(&builder, "\nFilename: %s\n", fileName) + for k, v := range fastResume { - fmt.Printf("%s: %v\n", k, v) + fmt.Fprintf(&builder, "%s: %v\n", k, v) } - fmt.Printf("\n") + + fmt.Print(builder.String()) return nil } diff --git a/main.go b/main.go index 64428db..906cba3 100644 --- a/main.go +++ b/main.go @@ -16,7 +16,7 @@ func main() { }, } - rootCmd.AddCommand(cmd.RunBencode()) + rootCmd.AddCommand(cmd.BencodeCommand()) if err := rootCmd.Execute(); err != nil { os.Exit(1)