diff --git a/.todo b/TODO similarity index 88% rename from .todo rename to TODO index 9fed410..b0d25f6 100644 --- a/.todo +++ b/TODO @@ -10,6 +10,8 @@ project: ☐ setup testing workflow ☐ setup go releaser workflow app/cmd/finder/file-finder.go: + ☐ Convert PTERM over to bubbletea (still researching the frameork) + ✔ Add ability to click on results @done(24-08-21 12:00) ☐ Add common funcs ☐ Application Banner ☐ implement display banner logic @@ -40,6 +42,8 @@ internal: utils.go: ✔ Adjust funcs based on FileSize option change @done(24-08-18 11:39) ✔ Adjust funcs to search by FileNameFilter option @done(24-08-18 11:39) + ☐ Fix table alignment issues + ☐ For detailed view add filesize total ☐ Create logic to find duplicate files ☐ hash comparison ☐ name similarities diff --git a/app/cmd/finder/file-finder.go b/app/cmd/finder/file-finder.go index 3cedfd7..e1f54e3 100644 --- a/app/cmd/finder/file-finder.go +++ b/app/cmd/finder/file-finder.go @@ -1,17 +1,13 @@ package main import ( - // "flag" "os" "reflect" "runtime" - // "strings" - "github.com/pterm/pterm" "github.com/spf13/cobra" - // "github.com/spf13/pflag" "github.com/spf13/viper" "file-finder/internal/types" @@ -20,7 +16,6 @@ import ( commonTypes "github.com/ondrovic/common/types" commonUtils "github.com/ondrovic/common/utils" commonCli "github.com/ondrovic/common/utils/cli" - // commonFormatters "github.com/ondrovic/common/utils/formatters" ) // #region Cli Setup @@ -71,12 +66,6 @@ func registerFloat64Flag(cmd *cobra.Command, name, shorthand string, value float cmd.Flags().Float64VarP(target, name, shorthand, value, usage+"\n") } -// func bindFlags(cmd *cobra.Command) { -// cmd.Flags().VisitAll(func(f *pflag.Flag) { -// viper.BindPFlag(f.Name, f) -// }) -// } - func newCompletionCmd() *cobra.Command { return &cobra.Command{ Use: "completion [bash|zsh|fish|powershell]", @@ -149,7 +138,7 @@ func run(cmd *cobra.Command, args []string) { FileSizeFilter: viper.GetString("file-size-filter"), FileTypeFilter: fileTypeFilter, ListDuplicateFiles: viper.GetBool("list-duplicate-files"), - RemoveFiles: removeFiles,//viper.GetBool("remove-files"), + RemoveFiles: removeFiles, ToleranceSize: viper.GetFloat64("tolerance-size"), OperatorTypeFilter: operatorType, Results: make(map[string][]string), @@ -170,34 +159,6 @@ func main() { } func Run(ff types.FileFinder) { - // fileSizeBytes, err := commonUtils.ConvertStringSizeToBytes(ff.FileSize) - - // if err != nil { - // pterm.Error.Printf("Error converting file size: %v\n", err) - // return - // } - - // // Format the file size for logging - // fileSizeStr := commonFormatters.FormatSize(fileSizeBytes) - // results, err := commonUtils.CalculateTolerances(fileSizeBytes, ff.ToleranceSize) - - // if err != nil { - // pterm.Error.Printf("Error calculating tolerances: %v\n", err) - // return - // } - - // // Calculate the tolerance size string - // toleranceSizeStr := "" - // if fileSizeStr != commonFormatters.FormatSize(results.LowerBoundSize) || fileSizeStr != commonFormatters.FormatSize(results.UpperBoundSize) { - // toleranceSizeStr = "( with a tolerance size of " + commonFormatters.FormatSize(results.LowerBoundSize) + " and " + commonFormatters.FormatSize(results.UpperBoundSize) + " )" - // } - - // pterm.Info.Printf("Searching for files of type %v %s %s %s...\n", - // ff.FileType, - // strings.ToLower(string(ff.OperatorType)), - // fileSizeStr, - // toleranceSizeStr, - // ) files, err := utils.FindAndDisplayFiles(ff) @@ -206,8 +167,6 @@ func Run(ff types.FileFinder) { return } - // fmt.Println(files) - if ff.RemoveFiles { utils.DeleteFiles(files) } diff --git a/internal/types/types.go b/internal/types/types.go index 1fe7add..9cb7363 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -31,8 +31,8 @@ type DirectoryResult struct { Count int } -// EntryResults struct for more in depth entry info -type EntryResults struct { +// EntryResult struct for more in depth entry info +type EntryResult struct { Directory string FileName string FileSize string diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 88bc5f1..a68dd20 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -4,31 +4,31 @@ import ( "errors" "fmt" "io" - "sort" - "strings" - - // "fmt" "os" "path/filepath" "reflect" "runtime" + "sort" + "strings" "sync" "file-finder/internal/types" - // commonTypes "github.com/ondrovic/common/types" commonUtils "github.com/ondrovic/common/utils" commonFormatters "github.com/ondrovic/common/utils/formatters" "github.com/jedib0t/go-pretty/v6/table" + "github.com/jedib0t/go-pretty/v6/text" "github.com/pterm/pterm" ) -var semaphore = make(chan struct{}, runtime.NumCPU()) +var ( + semaphore = make(chan struct{}, runtime.NumCPU()) +) // FindAndDisplayFiles gathers the results and displays them func FindAndDisplayFiles(ff types.FileFinder) (interface{}, error) { - results, count, err := getFiles(ff) + results, count, size, err := getFiles(ff) if err != nil { return nil, err } @@ -50,7 +50,7 @@ func FindAndDisplayFiles(ff types.FileFinder) (interface{}, error) { progressbar.Stop() if count > 0 { - renderResultsToTable(results, count, ff) + renderResultsToTable(results, count, size, ff) } else { pterm.Info.Printf("%d results found matching criteria\n", count) } @@ -68,45 +68,47 @@ func getResultsCount(results interface{}) (int, error) { } // getFiles handles getting the files based on the criteria -func getFiles(ff types.FileFinder) (interface{}, int, error) { - +func getFiles(ff types.FileFinder) (interface{}, int, int64, error) { entries, err := os.ReadDir(ff.RootDirectory) if err != nil { - return nil, 0, err + return nil, 0, 0, err } - var detailedResults []types.EntryResults + var detailedResults []types.EntryResult results := make(map[string][]string) var totalCount int + var totalFileSize int64 var mu sync.Mutex var wg sync.WaitGroup - // convert filter to bytes + // Convert filter to bytes var fileSize int64 if ff.FileSizeFilter != "" { var err error fileSize, err = commonUtils.ConvertStringSizeToBytes(ff.FileSizeFilter) if err != nil { - return nil, 0, err + return nil, 0, 0, err } } for _, entry := range entries { wg.Add(1) - go func(path string) { + go func(entry os.DirEntry) { defer wg.Done() + path := filepath.Join(ff.RootDirectory, entry.Name()) if entry.IsDir() { semaphore <- struct{}{} subFF := ff subFF.RootDirectory = path - subResult, subCount, err := getFiles(subFF) + subResult, subCount, subSize, err := getFiles(subFF) <-semaphore if err != nil { return } mu.Lock() if ff.DisplayDetailedResults { - detailedResults = append(detailedResults, subResult.([]types.EntryResults)...) + detailedResults = append(detailedResults, subResult.([]types.EntryResult)...) + totalFileSize += subSize } else { for dir, files := range subResult.(map[string][]string) { results[dir] = append(results[dir], files...) @@ -128,7 +130,6 @@ func getFiles(ff types.FileFinder) (interface{}, int, error) { if ff.FileSizeFilter != "" { sizeMatches, err = commonUtils.GetOperatorSizeMatches(ff.OperatorTypeFilter, fileSize, ff.ToleranceSize, size) if err != nil { - // pterm.Error.Printf("Error getting size matches: %v\n", err) pterm.Error.Println(err) return } @@ -137,7 +138,7 @@ func getFiles(ff types.FileFinder) (interface{}, int, error) { } } - // convert ToLower + // Convert ToLower lowerEntryName, err := commonFormatters.ToLower(entry.Name()) if err != nil { pterm.Error.Println(err) @@ -155,11 +156,12 @@ func getFiles(ff types.FileFinder) (interface{}, int, error) { mu.Lock() if ff.DisplayDetailedResults { - detailedResults = append(detailedResults, types.EntryResults{ + detailedResults = append(detailedResults, types.EntryResult{ Directory: ff.RootDirectory, FileName: entry.Name(), FileSize: commonFormatters.FormatSize(size), }) + totalFileSize += size // Accumulate the file size } else { results[ff.RootDirectory] = append(results[ff.RootDirectory], path) } @@ -167,18 +169,18 @@ func getFiles(ff types.FileFinder) (interface{}, int, error) { mu.Unlock() } } - }(filepath.Join(ff.RootDirectory, entry.Name())) + }(entry) // Pass entry to the goroutine } wg.Wait() if ff.DisplayDetailedResults { - return detailedResults, totalCount, nil + return detailedResults, totalCount, totalFileSize, nil } - return results, totalCount, nil + return results, totalCount, 0, nil } -func deleteEntryResults(entries []types.EntryResults) (int, []string) { +func deleteEntryResults(entries []types.EntryResult) (int, []string) { var deletedCount int var directoriesToRemove []string for _, entry := range entries { @@ -193,7 +195,6 @@ func deleteEntryResults(entries []types.EntryResults) (int, []string) { return deletedCount, directoriesToRemove } - // BUG: when doing the directory result it doesn't list the files so you end up deleting the entire directory of files ;-( // going to think on how I want to do this, but for now I am just doing to disable -r unless -d is used // func deleteDirectoryResults(dirResults []types.DirectoryResult) (int, []string) { @@ -229,7 +230,6 @@ func deleteEntryResults(entries []types.EntryResults) (int, []string) { // return deletedCount, directoriesToRemove // } - func deleteFileBasedOnResults(results interface{}) error { spinner, _ := pterm.DefaultSpinner.Start("Deleting files and directories...") defer spinner.Stop() @@ -238,8 +238,9 @@ func deleteFileBasedOnResults(results interface{}) error { var directoriesToRemove []string switch v := results.(type) { - case []types.EntryResults: + case []types.EntryResult: deletedFileCount, directoriesToRemove = deleteEntryResults(v) + // Part of the bug related to the func above // case []types.DirectoryResult: // deletedFileCount, directoriesToRemove = deleteDirectoryResults(v) default: @@ -335,6 +336,7 @@ func DeleteFiles(results interface{}) { if resultCount > 0 { // Confirm deletion with the user (you may want to uncomment this if needed) result, _ := pterm.DefaultInteractiveConfirm.Show("Are you sure you want to delete these files?") + // for debugging since you cannot interact // result := true if !result { pterm.Info.Println("Deletion cancelled.") @@ -357,31 +359,59 @@ func processResults(results map[string][]string) []types.DirectoryResult { return processedResults } -// renderResultsToTable renders the results into a formatted table -func renderResultsToTable(results interface{}, totalCount int, ff types.FileFinder) { - t := table.NewWriter() - t.SetOutputMirror(os.Stdout) +func formatResultHyperLink(link, txt string) string { + text.EnableColors() - if ff.DisplayDetailedResults { - t.AppendHeader(table.Row{"Directory", "FileName", "FileSize"}) - for _, result := range results.([]types.EntryResults) { + link = commonFormatters.FormatPath(link, runtime.GOOS) + txt = text.FgGreen.Sprint(txt) + + return text.Hyperlink(link, txt) +} + +func renderResultsToTable(results interface{}, totalCount int, totalFileSize int64, ff types.FileFinder) { + t := table.Table{} + + // Determine header and footer based on the type of results + var header table.Row + var footer table.Row + switch results.(type) { + case []types.DirectoryResult: + header = table.Row{"Directory", "Count"} + footer = table.Row{"Total", pterm.Sprintf("%v", totalCount)} + case []types.EntryResult: + header = table.Row{"Directory", "FileName", "FileSize"} + footer = table.Row{"Total", pterm.Sprintf("%v", totalCount), pterm.Sprintf("%v", commonFormatters.FormatSize(totalFileSize))} + default: + return // Exit if results type is not supported + } + + t.AppendHeader(header) + + // Append rows based on the display mode + switch results := results.(type) { + case []types.DirectoryResult: + for _, result := range results { t.AppendRow(table.Row{ - commonFormatters.FormatPath(result.Directory, runtime.GOOS), - result.FileName, - result.FileSize, + formatResultHyperLink(result.Directory, result.Directory), + pterm.Sprintf("%v", result.Count), }) } - } else { - t.AppendHeader(table.Row{"Directory", "Count"}) - for _, result := range results.([]types.DirectoryResult) { - t.AppendRow(table.Row{ - commonFormatters.FormatPath(result.Directory, runtime.GOOS), - result.Count, - }) + case []types.EntryResult: + if ff.DisplayDetailedResults { + for _, result := range results { + newLink := pterm.Sprintf("%s/%s", result.Directory, result.FileName) + t.AppendRow(table.Row{ + formatResultHyperLink(result.Directory, result.Directory), + formatResultHyperLink(newLink, result.FileName), + result.FileSize, + }) + } } } - t.AppendFooter(table.Row{"Total", totalCount}) + t.AppendFooter(footer) + t.SetStyle(table.StyleColoredDark) + t.SetOutputMirror(os.Stdout) t.Render() }