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, .wmv, .3gp)", "Image files (.jpg, .jpeg, .png, .gif)", "Microsoft documents (.doc, .docx, .xls, .xlsx, .ppt, .pptx)", "Archive files (.zip, .rar, .7z, .tar, .gz, .bz2)", "Audio files (.mp3, .wav, .flac, .aac, .ogg)", "PDF documents (.pdf)", "Text files (.txt, .md, .rtf)", "Log files (.log)", "Temporary files (.tmp, .temp, .swp)", "Backup files (.bak, .backup, .old)", "Executable files (.exe, .msi, .dmg, .pkg, .deb, .rpm)", "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 extLookup[".wmv"] = true extLookup[".3gp"] = true } if strings.Contains(s, "image") { extLookup[".jpg"] = true extLookup[".jpeg"] = true extLookup[".png"] = true extLookup[".gif"] = true } if strings.Contains(s, "microsoft") { extLookup[".doc"] = true extLookup[".docx"] = true extLookup[".xls"] = true extLookup[".xlsx"] = true extLookup[".ppt"] = true extLookup[".pptx"] = true } if strings.Contains(s, "archive") { extLookup[".zip"] = true extLookup[".rar"] = true extLookup[".7z"] = true extLookup[".tar"] = true extLookup[".gz"] = true extLookup[".bz2"] = true } if strings.Contains(s, "audio") { extLookup[".mp3"] = true extLookup[".wav"] = true extLookup[".flac"] = true extLookup[".aac"] = true extLookup[".ogg"] = true } if strings.Contains(s, "pdf") { extLookup[".pdf"] = true } if strings.Contains(s, "text") { extLookup[".txt"] = true extLookup[".md"] = true extLookup[".rtf"] = true } if strings.Contains(s, "log") { extLookup[".log"] = true } if strings.Contains(s, "temporary") || strings.Contains(s, "temp") { extLookup[".tmp"] = true extLookup[".temp"] = true extLookup[".swp"] = true } if strings.Contains(s, "backup") { extLookup[".bak"] = true extLookup[".backup"] = true extLookup[".old"] = true } if strings.Contains(s, "executable") { extLookup[".exe"] = true extLookup[".msi"] = true extLookup[".dmg"] = true extLookup[".pkg"] = true extLookup[".deb"] = true extLookup[".rpm"] = 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 canceled 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 canceled 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.