package copy import ( "fmt" "io" "os" "sync" "github.com/akamensky/argparse" "github.com/rs/zerolog/log" "gitlab.com/ChaotiCryptidz/musicutil/types" "gitlab.com/ChaotiCryptidz/musicutil/utils" "gitlab.com/ChaotiCryptidz/musicutil/utils/transcoder" ) type CopyCommandArgs struct { Source *string Dest *string TranscodePreset *string Threads *int SkipExisting *bool SingleDirectory *bool } func dirNotExistValidator(directory string) error { fileInfo, err := os.Stat(directory) if err != nil { return fmt.Errorf("%s does not exist", directory) } if !fileInfo.IsDir() { return fmt.Errorf("%s is not a directory", directory) } return nil } func fileExists(filepath string) bool { if _, err := os.Stat(filepath); err == nil { return true } else { return false } } func copyFile(src, dest string) error { _, err := os.Stat(src) if err != nil { return err } source, err := os.Open(src) if err != nil { return err } defer source.Close() destination, err := os.Create(dest) if err != nil { return err } defer destination.Close() _, err = io.Copy(destination, source) return err } func RegisterCopyCommand(parser *argparse.Parser) (*argparse.Command, *CopyCommandArgs) { cmd := parser.NewCommand("copy", "copies files with or without transcoding") arguments := &CopyCommandArgs{} arguments.Source = cmd.String("s", "src", &argparse.Options{ Required: true, Validate: func(args []string) error { return dirNotExistValidator(args[0]) }, }) arguments.Dest = cmd.String("d", "dest", &argparse.Options{ Required: true, Validate: func(args []string) error { return dirNotExistValidator(args[0]) }, }) arguments.TranscodePreset = cmd.String("p", "transcode-preset", &argparse.Options{ Required: true, Validate: func(args []string) error { if args[0] == "list" { transcoder.PrintTranscodePresets() os.Exit(0) } else if args[0] == "copy" { return nil } if _, err := transcoder.GetPresetByName(args[0]); err != nil { return fmt.Errorf("Preset does not exist") } return nil }, }) arguments.SkipExisting = cmd.Flag("", "skip-existing", &argparse.Options{ Default: false, }) arguments.SingleDirectory = cmd.Flag("", "single-directory", &argparse.Options{ Default: false, }) arguments.Threads = cmd.Int("", "threads", &argparse.Options{ Default: 0, }) return cmd, arguments } type CopyCommand struct { Args *CopyCommandArgs Files []*types.File } func NewCopyCommand(args *CopyCommandArgs) *CopyCommand { return &CopyCommand{ Args: args, Files: make([]*types.File, 0), } } func (c *CopyCommand) scan_for_music() { log.Info().Msg("Scanning For Music") files, err := utils.ScanForMusic(*c.Args.Source) if err != nil { panic(err) } c.Files = files } func (c *CopyCommand) check_for_duplicates() { log.Info().Msg("Creating For Duplicates") seen := make(map[string]bool) dupes := make([]string, 0) for _, file := range c.Files { filename := file.JoinFilename() if _, ok := seen[filename]; ok { dupes = append(dupes, filename) } else { seen[filename] = true } } if len(dupes) > 0 { log.Fatal().Strs("dupes", dupes).Msg("Duplicates Found, please rename/remove.") } } func (c *CopyCommand) create_directories() { log.Info().Msg("Creating Directories") directories := make(map[string]bool) for _, file := range c.Files { directories[file.PathFromSource] = true } for dir := range directories { if file_info, err := os.Stat(*c.Args.Dest + "/" + dir); err == nil { if file_info.IsDir() { continue } } log.Info().Str("dir", dir).Msg("Creating Directory") err := os.MkdirAll(*c.Args.Dest+"/"+dir, os.ModePerm) if err != nil { log.Fatal().Err(err).Msg("Could not create directory") } } } func (c *CopyCommand) _transcode_file(file *types.File, trans_config *transcoder.TranscodeConfig, is_threaded bool) { new_filename := file.Filename + "." + trans_config.FileExtension dest_filepath := *c.Args.Dest + "/" if *c.Args.SingleDirectory { dest_filepath = dest_filepath + new_filename } else { dest_filepath = dest_filepath + file.PathFromSource + "/" + new_filename } if *c.Args.SkipExisting && fileExists(dest_filepath) { log.Info().Str("file", new_filename). Msg("Skipping transcode as file already exists") return } else { log.Info().Str("file", new_filename).Msg("Transcoding") } output, err := transcoder.Transcode(file, trans_config, nil, dest_filepath) if err != nil { log.Fatal().Err(err).Str("output", output).Msg("Transcode Failed") } else if is_threaded { log.Info().Str("file", new_filename).Msg("Finished Transcoding") } } func (c *CopyCommand) transcode_files() { log.Info().Msg("Transcoding Files") // TODO: Implement custom transcode config file trans_preset, err := transcoder.GetPresetByName(*c.Args.TranscodePreset) if err != nil { log.Fatal().Err(err).Msg("Could not get transcode preset") } if *c.Args.Threads == 0 { for _, file := range c.Files { c._transcode_file(file, trans_preset.Config, false) } } else { var wg sync.WaitGroup threads := *c.Args.Threads jobs := make(chan *types.File, threads) for i := 1; i <= threads; i++ { go func() { for file := range jobs { defer wg.Done() c._transcode_file(file, trans_preset.Config, true) } }() } for _, file := range c.Files { wg.Add(1) jobs <- file } close(jobs) wg.Wait() } } func (c *CopyCommand) _copy_file(file *types.File) { src_filepath := file.JoinPathTo() dest_filepath := "" if *c.Args.SingleDirectory { dest_filepath = *c.Args.Dest + "/" + file.JoinFilename() } else { dest_filepath = *c.Args.Dest + "/" + file.JoinPathFromSource() } exists := fileExists(dest_filepath) if exists { log.Info().Str("file", dest_filepath). Msg("Skipping as already exists in destination") } else { log.Info(). Str("file", file.JoinFilename()). Msg("Copying File") err := copyFile(src_filepath, dest_filepath) if err != nil { log.Panic(). Err(err). Str("file", file.JoinFilename()). Msg("Error Copying File") } } } func (c *CopyCommand) copy_files() { log.Info().Msg("Copying Files Into Dest") for _, file := range c.Files { c._copy_file(file) } } func (c *CopyCommand) Run() { log.Info().Msg("Copying Files") c.scan_for_music() if !*c.Args.SingleDirectory { c.create_directories() } else { c.check_for_duplicates() } if *c.Args.TranscodePreset == "copy" { c.copy_files() } else { c.transcode_files() } }