Refactor categorization to use a callback instead of aggregating the results

This will prepare for taking user input, as we don't want to continue scanning  before we can find out if a user wants to back out
master
Nick Krichevsky 2021-09-09 23:19:09 -04:00
parent 08089c4bd9
commit f21ae2e502
2 changed files with 58 additions and 28 deletions

View File

@ -16,16 +16,23 @@ type fileLocation struct {
location tv.Location
}
// categorizationResult is the result of an individual categorization; it's effectively what a return value would be
// for a single operation, but it's returned through a callback
type categorizationResult struct {
locations []fileLocation
// the location of the categorized file. This should be ignored if the error is non-nil, or "skipped" is true
location fileLocation
err error
}
type categorizationStats struct {
filesScanned int
filesSkipped int
}
// Performs the categorization process for the given source (i.e. where the downloaded files are stored)
// and library (i.e. where they will be sorted into). Errors that halt the program proceeding will be returned. Those
// that do not will be printed to stderr.
func categorizeFolder(sourceFolder, libraryFolder string) (categorizationResult, error) {
// and library (i.e. where they will be sorted into). Errors that halt the program proceeding will be returned. Results
// and non-fatal errors will be returned via resultCallback. If this callback returns false, categorization stops.
func categorizeFolder(sourceFolder, libraryFolder string, resultCallback func(categorizationResult) bool) (categorizationStats, error) {
sourceFolderContents, err := getDirEntries(sourceFolder, func(entries []os.FileInfo) {
// Sort from newest to oldest
sort.Slice(entries, func(i, j int) bool {
@ -37,40 +44,54 @@ func categorizeFolder(sourceFolder, libraryFolder string) (categorizationResult,
})
if err != nil {
return categorizationResult{}, fmt.Errorf("failed to read source folder: %w", err)
return categorizationStats{}, fmt.Errorf("failed to read source folder: %w", err)
}
categorizer, err := categorize.NewCategorizer(libraryFolder)
if err != nil {
return categorizationResult{}, fmt.Errorf("failed to make categorizer: %w", err)
return categorizationStats{}, fmt.Errorf("failed to make categorizer: %w", err)
}
locations := performCategorization(categorizer, sourceFolderContents)
return categorizationResult{
locations: locations,
filesScanned: len(locations),
filesSkipped: len(sourceFolderContents) - len(locations),
}, nil
stats := categorizationStats{}
performCategorization(categorizer, sourceFolderContents, func(res categorizationResult) bool {
stats.filesScanned++
if errors.Is(err, match.ErrNoMatches) {
stats.filesSkipped++
return true
} else if res.err != nil {
stats.filesSkipped++
return true
}
return resultCallback(res)
})
return stats, nil
}
// performCategorization categorizes the given files through the given categorizer, converting them to a
// slice of FileLocations, ordered as they were provided in sourceFolderContents.
// When a file fails to be read, an error will be printed to stderr.
func performCategorization(categorizer categorize.Categorizer, sourceFolderContents []string) []fileLocation {
locations := []fileLocation{}
// slice of FileLocations, ordered as they were provided in sourceFolderContents. Results and non-fatal errors will be
// returned via resultCallback. If this callback returns false, categorization stops.
func performCategorization(categorizer categorize.Categorizer, sourceFolderContents []string, resultCallback func(categorizationResult) bool) {
for _, entry := range sourceFolderContents {
location, err := categorizer.CategorizeFile(entry)
if errors.Is(err, match.ErrNoMatches) {
continue
} else if err != nil {
fmt.Fprintf(os.Stderr, "Failed to categorize '%s':\n\t%s\n", entry, err)
continue
var wrappedErr error
if err != nil {
wrappedErr = fmt.Errorf("failed to categorize '%s': %w", entry, err)
}
locations = append(locations, fileLocation{entry, location})
}
shouldContinue := resultCallback(categorizationResult{
location: fileLocation{entry, location},
err: wrappedErr,
})
return locations
if shouldContinue {
continue
} else {
break
}
}
}
// getDirEntries gets filenames in a directory, ordered by the given sort function.

View File

@ -17,16 +17,25 @@ func main() {
sourceFolder, libraryFolder := os.Args[1], os.Args[2]
categorizationRes, err := categorizeFolder(sourceFolder, libraryFolder)
locations := []fileLocation{}
categorizationStats, err := categorizeFolder(sourceFolder, libraryFolder, func(res categorizationResult) bool {
if res.err != nil {
fmt.Fprintln(os.Stderr, res.err)
} else {
locations = append(locations, res.location)
}
return true
})
if err != nil {
panic(err)
}
prettyPrintLocations(categorizationRes.locations)
fmt.Fprintf(os.Stderr, "%d items not categorized\n", categorizationRes.filesSkipped)
prettyPrintLocations(locations)
fmt.Fprintf(os.Stderr, "%d items not categorized\n", categorizationStats.filesSkipped)
}
func prettyPrintLocations(fileLocations []fileLocation) {
for _, fl := range fileLocations {
filename, location := fl.filename, fl.location