Skip to content

Commit

Permalink
refactor: edit command and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
zze0s committed May 18, 2024
1 parent 9949e6e commit 2b0a8b0
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 82 deletions.
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
```
135 changes: 98 additions & 37 deletions cmd/bencode.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ package cmd
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"

"migraterr/internal/bencoding"

"github.com/pkg/errors"
"github.com/spf13/cobra"
)

func RunBencode() *cobra.Command {

func BencodeCommand() *cobra.Command {
command := &cobra.Command{
Use: "bencode",
Short: "bencoded tools",
Expand All @@ -25,101 +27,160 @@ 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")
}

// validate replaceStrings
if len(replaceStrings) > 0 {
for _, replacement := range replaceStrings {
if !strings.Contains(replacement, "|") {
return errors.Errorf("invalid replacement: %q", replacement)
}

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],
})
}
}

processedFiles := 0

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 != "" {
dir := filepath.Dir(exportDir)
err = os.MkdirAll(dir, os.ModePerm)
if err != nil {
return errors.Wrapf(err, "could not create export dir: %q", exportDir)
}
}

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)
exportPath := file
if exportDir != "" {
exportPath = filepath.Join(exportDir, fileName)
}

if verbose {
log.Printf("[%d/%d] reading file %s\n", idx, len(files), fileName)
}

err = openAndProcessFile(file, exportPath, replacements, verbose, dry)
if err != nil {
return errors.Wrapf(err, "could not open and process file: %q", file)
}

processedFiles++

if verbose {
log.Printf("[%d/%d] sucessfully processed file %s\n", len(files), processedFiles, fileName)
log.Printf("[%d/%d] sucessfully processed file %s\n", processedFiles, len(files), fileName)
}

}

log.Printf("migraterr bencode processed %d files\n", processedFiles)

return nil
}

return command
}

func RunBencodeInfo() *cobra.Command {
func openAndProcessFile(filePath string, exportPath string, replacements []bencoding.Replacement, verbose bool, dry bool) error {
fileReader, err := os.Open(filePath)
if err != nil {
return errors.Wrapf(err, "could not read filePath: %s", filePath)
}

defer fileReader.Close()

err = bencoding.ProcessFile(fileReader, filePath, exportPath, replacements, verbose, dry)
if err != nil {
return errors.Wrapf(err, "error processing filePath: %s", filePath)
}

return 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 {
Expand Down
86 changes: 45 additions & 41 deletions internal/bencoding/bencode.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package bencoding
import (
"bufio"
"fmt"
"io"
"log"
"os"
"path/filepath"
Expand All @@ -12,57 +13,39 @@ 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) 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)
decoder := bencode.NewDecoder(reader)

err := decoder.Decode(&decodedFile)
if err != nil {
return errors.Wrapf(err, "could not decode file: %s", filePath)
}

if len(replacements) > 0 {
for k, v := range decodedFile {
for _, replacement := range replacements {
if !strings.Contains(replacement, "|") {
continue
}

parts := strings.Split(replacement, "|")

if len(parts) == 0 || len(parts) > 2 {
continue
}

switch val := v.(type) {
case string:
if strings.Contains(val, parts[0]) {
decodedFile[k] = strings.Replace(val, parts[0], parts[1], -1)
if strings.Contains(val, replacement.Old) {
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("replaced: %q with %q\n", replacement.Old, replacement.New)
}
}
default:
Expand All @@ -73,14 +56,17 @@ func Process(filePath, exportDir string, replacements []string, verbose, dry boo
}
}

if err := decodedFile.EncodeAndWrite(exportPath); err != nil {
log.Printf("could not write fastresume file %s error: %q\n", exportPath, err)
return err
exportFile, err := os.Create(exportPath)
if err != nil {
return errors.Wrapf(err, "could not create file: %s", exportPath)
}
}

if verbose {
log.Printf("sucessfully processed file %s\n", exportPath)
defer exportFile.Close()

err = decodedFile.EncodeAndWriteFile(exportFile)
if err != nil {
return errors.Wrapf(err, "could not write fastresume file %s", exportPath)
}
}

return nil
Expand Down Expand Up @@ -109,6 +95,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)
Expand Down Expand Up @@ -145,18 +145,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
}
Loading

0 comments on commit 2b0a8b0

Please sign in to comment.