Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
View Source
var FilesCmd = &cobra.Command{ Use: "files", Short: "A command for managing files", Long: `This command will help manage various aspects of file operations on MacOS and Linux systems.`, Run: func(cmd *cobra.Command, args []string) { err := cmd.Help() cobra.CheckErr(err) }, }
View Source
var FindAndDeleteCmd = &cobra.Command{ Use: "findAndDelete [directory]", Short: "Find and delete files of selected types, or list extensions", Long: `Recursively scan the provided directory for files of types selected by the user and delete them after an interactive confirmation. Use --list-extensions to list all file extensions in the directory instead. Use --filename to target a specific filename, --glob for glob patterns, or --ext for file extensions. `, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { dir := args[0] isVerbose := utils.IsVerbose(cmd) if _, err := os.Stat(dir); os.IsNotExist(err) { log.Error("Provided directory does not exist: %s", dir) return } if listExtensions { extensions, err := ListExtensions(dir) if err != nil { log.Error("Error listing extensions: %v", err) return } log.Message("File extensions found in %s:", dir) for _, ext := range extensions { log.Message(" - %s", ext) } return } matchFn, err := buildMatchFunction(globPattern, extension, filename) if err != nil { log.Error("Error building match function: %v", err) return } if matchFn == nil { options := []string{ "JSON files (.json)", "Video files (.mp4, .mov, .avi, .mkv, .m4v)", "Image files (.jpg, .jpeg, .png, .gif)", "System files (.DS_Store)", } var selected []string prompt := &survey.MultiSelect{ Message: "Select file types to find and delete:", Options: options, } if err := survey.AskOne(prompt, &selected); err != nil { log.Error("Error collecting selection: %v", err) return } if len(selected) == 0 { log.Info("No file types selected. Nothing to do.") return } extLookup := map[string]bool{} for _, s := range selected { s = strings.ToLower(s) if strings.Contains(s, "json") { extLookup[".json"] = true } if strings.Contains(s, "video") { extLookup[".mp4"] = true extLookup[".mov"] = true extLookup[".avi"] = true extLookup[".mkv"] = true extLookup[".m4v"] = true } if strings.Contains(s, "image") { extLookup[".jpg"] = true extLookup[".jpeg"] = true extLookup[".png"] = true extLookup[".gif"] = true } if strings.Contains(s, "system") { extLookup[".ds_store"] = true } } matchFn = func(name string) bool { return extLookup[strings.ToLower(filepath.Ext(name))] } } log.Start("Scanning for files...") spinner := utils.NewProgressSpinner("Scanning directories...") matches, totalSize, walkErr := ScanFiles(dir, matchFn, spinner) if walkErr != nil { spinner.Stop() log.Error("Error scanning directory: %v", walkErr) return } spinner.UpdateMessage("Scan complete.") spinner.SetProgressBar(1.0) spinner.Stop() if walkErr != nil { log.Error("Error scanning directory: %v", walkErr) return } if len(matches) == 0 { log.Success("No matching files found in %s.", dir) return } log.Message("\nFound %d file(s) (%.2f MB total):", len(matches), float64(totalSize)/(1024*1024)) for _, m := range matches { if isVerbose { log.Message(" - %s", m) } else { log.Message(" - %s", filepath.Base(m)) } } confirm := false promptConfirm := &survey.Confirm{ Message: fmt.Sprintf("Delete %d file(s) (%.2f MB)?", len(matches), float64(totalSize)/(1024*1024)), Default: false, } if err := survey.AskOne(promptConfirm, &confirm); err != nil { log.Error("Error during confirmation prompt: %v", err) return } if !confirm { log.Info("Deletion cancelled by user.") return } // Attempt parallel deletion var deleted, errors atomic.Int64 var wg sync.WaitGroup workerCount := 4 fileChan := make(chan string, len(matches)) for i := 0; i < workerCount; i++ { wg.Add(1) go func() { defer wg.Done() for f := range fileChan { if f == "" { continue } if err := os.Remove(f); err != nil { if !os.IsNotExist(err) { log.Error("Failed to delete %s: %v", f, err) errors.Add(1) } } else { if isVerbose { log.Success("Deleted %s", f) } deleted.Add(1) } } }() } for _, f := range matches { fileChan <- f } close(fileChan) wg.Wait() log.Success("Done. Deleted %d file(s), %d errors.", deleted.Load(), errors.Load()) }, }
View Source
var FindNonMovieFoldersCmd = &cobra.Command{ Use: "findNonMovieFolders [directory]", Short: "Find and optionally delete non-movie folders", Long: `This command searches recursively through the supplied directory for directories that do not contain video files (mp4, mkv, avi, mov, wmv, flv, webm, mpeg, mpg, m4v). It identifies top-level subdirectories within the supplied directory that lack any such files anywhere within their structure. It lists the files within the identified folders and prompts for confirmation before deletion.`, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { log.Start("Scanning for non-movie folders...") directory := args[0] isVerbose := utils.IsVerbose(cmd) if _, err := os.Stat(directory); os.IsNotExist(err) { log.Error("Provided directory does not exist: %s", directory) return } log.Verbose(isVerbose, "Searching for directories in: %s", directory) spinner := utils.NewProgressSpinner("Scanning directories...") nonMovieFolders, err := findNonMovieFolders(isVerbose, directory, spinner, func(done, total int) { progress := 0.0 if total > 0 { progress = float64(done) / float64(total) } spinner.SetProgressBar(progress, fmt.Sprintf("Scanning... (%d/%d)", done, total)) }) if err != nil { spinner.Stop() log.Error("Error finding non-movie folders: %s", err) return } spinner.UpdateMessage("Scan complete.") spinner.SetProgressBar(1.0) spinner.Stop() log.Verbose(isVerbose, "Found %d potential non-movie folders.", len(nonMovieFolders)) if len(nonMovieFolders) == 0 { log.Success("No non-movie folders found in %s.", directory) return } log.Message("\nFound %d non-movie folder(s) that will be deleted:", len(nonMovieFolders)) // Store folder contents for summary and confirmation type FolderContent struct { Files []string Error string } folderContents := make(map[string]FolderContent) for _, folder := range nonMovieFolders { var files []string walkErr := filepath.WalkDir(folder, func(path string, d os.DirEntry, err error) error { if err != nil { return err } if !d.IsDir() { relPath, err := filepath.Rel(folder, path) if err != nil { return err } files = append(files, relPath) } return nil }) listErrorString := "" if walkErr != nil { errMsg := fmt.Sprintf("Could not list files in folder %s: %s", folder, walkErr) log.Warn(errMsg) listErrorString = "(error listing files)" } folderContents[folder] = FolderContent{Files: files, Error: listErrorString} log.Message(" - %s", folder) if listErrorString != "" { log.Message(" %s", listErrorString) } else if len(files) > 0 { for _, file := range files { displayPath := filepath.Join(filepath.Base(folder), file) log.Message(" - %s", displayPath) } } else { log.Message(" (Contains no files or only empty subdirectories)") } } log.Message("") if !askForConfirmation("Do you want to delete these folders and their contents?") { log.Info("Deletion cancelled by user.") return } log.Start("Deleting non-movie folders...") deletedCount := 0 skippedCount := 0 errorMessages := []string{} for _, folder := range nonMovieFolders { log.Warn("Attempting to delete: %s", folder) if err := os.RemoveAll(folder); err != nil { errMsg := fmt.Sprintf("Error deleting folder %s: %s", folder, err) log.Error(errMsg) errorMessages = append(errorMessages, errMsg) skippedCount++ } else { log.Success("Deleted: %s", folder) deletedCount++ } } if len(errorMessages) > 0 { log.Warn("Encountered %d error(s) during processing.", len(errorMessages)) } log.Success("Processing complete. Deleted %d folder(s), skipped %d due to errors.", deletedCount, skippedCount) }, }
FindNonMovieFoldersCmd defines the cobra command for finding and optionally deleting directories that do not contain common video file types.
Functions ¶
func ListExtensions ¶ added in v0.25.16
Types ¶
Click to show internal directories.
Click to hide internal directories.