diff --git a/src/CCVTAC.Main/Downloading/Downloader.fs b/src/CCVTAC.Main/Downloading/Downloader.fs index 8b65a48..239a111 100644 --- a/src/CCVTAC.Main/Downloading/Downloader.fs +++ b/src/CCVTAC.Main/Downloading/Downloader.fs @@ -8,7 +8,6 @@ open CCVTAC.Main.Downloading.Downloading open CCVTAC.Main.ExternalTools open CCVTAC.Main.Settings.Settings open CCFSharpUtils -open CCFSharpUtils.Text open FsToolkit.ErrorHandling open System @@ -37,7 +36,7 @@ module Downloader = "--write-thumbnail --convert-thumbnails jpg" writeJsonArg trimFileNamesArg - "--retries 2" ] + "--retries 3" ] |> Set.ofList if userSettings.QuietMode then @@ -87,7 +86,7 @@ module Downloader = let commandWithArgs = $"{programName} {args}" let downloadSettings = ToolSettings.create commandWithArgs userSettings.WorkingDirectory - let downloadResult = runTool downloadSettings [1] printer + let downloadResult = runTool printer downloadSettings [1] let anyFilesDownloaded = Num.isPos <| audioFileCount userSettings.WorkingDirectory Files.audioFileExts match downloadResult, anyFilesDownloaded with @@ -128,19 +127,20 @@ module Downloader = let args = generateDownloadArgs None userSettings None (Some [url']) let commandWithArgs = $"{programName} {args}" let downloadSettings = ToolSettings.create commandWithArgs userSettings.WorkingDirectory - let metadataDownloadResult = runTool downloadSettings [1] printer + let metadataDownloadResult = runTool printer downloadSettings [1] match metadataDownloadResult with | Ok _ -> Ok "Supplementary metadata download completed OK." | Error err -> Error [$"Supplementary metadata download failed: {err}"] - let run (mediaType: MediaType) userSettings (printer: Printer) : Result = + let run (printer: Printer) (mediaType: MediaType) userSettings : Result = result { let rawUrls = generateDownloadUrl mediaType let urls = { Primary = PrimaryUrl rawUrls[0] Metadata = SupplementaryUrl <| if rawUrls.Length = 2 then Some rawUrls[1] else None } - let! _ = downloadMedia printer mediaType userSettings urls.Primary + let! warnings = downloadMedia printer mediaType userSettings urls.Primary + warnings |> List.iter printer.Info let! metadataDownloadResult = downloadMetadata printer userSettings urls.Metadata return! Ok metadataDownloadResult } diff --git a/src/CCVTAC.Main/Downloading/Updater.fs b/src/CCVTAC.Main/Downloading/Updater.fs index 3d937e6..090825b 100644 --- a/src/CCVTAC.Main/Downloading/Updater.fs +++ b/src/CCVTAC.Main/Downloading/Updater.fs @@ -7,24 +7,24 @@ open CCFSharpUtils.Text module Updater = - let run userSettings (printer: Printer) : Result = + let successExitCode = 0 + + let run (printer: Printer) userSettings : unit = if String.hasNoText userSettings.DownloaderUpdateCommand then - printer.Info("No downloader update command provided, so will skip.") - Ok() + printer.Info "No downloader update command provided, so will skip." else - let toolSettings = ToolSettings.create userSettings.DownloaderUpdateCommand userSettings.WorkingDirectory - - match Runner.runTool toolSettings [] printer with - | Ok result -> - if result.ExitCode <> 0 then - printer.Warning("Tool updated with minor issues.") - - match result.Error with - | Some w -> printer.Warning w - | None -> () + let toolSettings = ToolSettings.create userSettings.DownloaderUpdateCommand + userSettings.WorkingDirectory - Ok() + let executionResult = Runner.runTool printer toolSettings [] - | Error err -> - printer.Error $"Failure updating: {err}" - Error err + match executionResult with + | Ok details -> + if details.ExitCode <> successExitCode then + match details.Error with + | Some errMsg -> $"Update completed with minor issues: {errMsg}" + | None -> "Update completed with minor unspecified issues." + |> printer.Warning + printer.EmptyLine() + | Error msg -> + printer.Error($"Failure updating: {msg}", ?appendLines = Some 1uy) diff --git a/src/CCVTAC.Main/ExternalTools/Runner.fs b/src/CCVTAC.Main/ExternalTools/Runner.fs index 9ce28ec..1c99276 100644 --- a/src/CCVTAC.Main/ExternalTools/Runner.fs +++ b/src/CCVTAC.Main/ExternalTools/Runner.fs @@ -21,7 +21,7 @@ module Runner = /// Tool settings for execution /// Additional exit codes, other than 0, that can be treated as non-failures /// - let runTool toolSettings otherSuccessExitCodes (printer: Printer) : Result = + let runTool (printer: Printer) toolSettings otherSuccessExitCodes : Result = let watch = Watch() printer.Info $"Running {toolSettings.CommandWithArgs}..." diff --git a/src/CCVTAC.Main/History.fs b/src/CCVTAC.Main/History.fs index c803126..d16c6cf 100644 --- a/src/CCVTAC.Main/History.fs +++ b/src/CCVTAC.Main/History.fs @@ -19,7 +19,7 @@ type History(filePath: string, displayCount: int) = member this.Append(url: string, entryTime: DateTime, printer: Printer) : unit = try let serializedTime = JsonSerializer.Serialize(entryTime).Replace("\"", "") - let text = serializedTime + string separator + url + String.newLine + let text = serializedTime + string separator + url + String.nl match appendToFile this.FileInfo text with | Ok _ -> printer.Debug $"Added \"%s{url}\" to the history log." @@ -54,9 +54,9 @@ type History(filePath: string, displayCount: int) = for dateTime, urls in historyData do let formattedTime = sprintf "%s" (dateTime.ToString("yyyy-MM-dd HH:mm:ss")) - let joinedUrls = String.Join(String.newLine, urls) + let joinedUrls = String.Join(String.nl, urls) table.AddRow(formattedTime, joinedUrls) |> ignore - Printer.PrintTable table + printer.PrintTable table with exn -> printer.Error $"Could not display history: %s{exn.Message}" diff --git a/src/CCVTAC.Main/IoUtilities/Directories.fs b/src/CCVTAC.Main/IoUtilities/Directories.fs index acd98fa..c5811a4 100644 --- a/src/CCVTAC.Main/IoUtilities/Directories.fs +++ b/src/CCVTAC.Main/IoUtilities/Directories.fs @@ -5,7 +5,6 @@ open CCFSharpUtils open CCFSharpUtils.Collections open CCFSharpUtils.Text open System.IO -open System.Text module Directories = @@ -13,30 +12,24 @@ module Directories = let private allFilesSearchPattern = "*" /// Counts the number of audio files in a directory. - let audioFileCount (directory: string) (includedExtensions: string list) = - DirectoryInfo(directory).EnumerateFiles() + let audioFileCount dirName includedExtensions = + DirectoryInfo(dirName).EnumerateFiles() |> Seq.filter (fun f -> List.containsIgnoreCase f.Extension includedExtensions) |> Seq.length /// Returns the filenames in a given directory, optionally ignoring specific filenames. - let private getDirectoryFileNames - (directoryName: string) - (customIgnoreFiles: string seq option) - : Result = - - let ignoreFiles = - customIgnoreFiles - |> Option.defaultValue Seq.empty + let private getDirectoryFileNames dirName ignoreFilesOpt : Result = + let ignoreFiles : string list = + ignoreFilesOpt + |> Option.defaultValue List.empty |> Seq.distinct - |> Seq.toArray + |> Seq.toList ofTry (fun _ -> - Directory.GetFiles(directoryName, allFilesSearchPattern, EnumerationOptions()) - |> Array.filter (fun filePath -> not (ignoreFiles |> Array.exists filePath.EndsWith))) - - let deleteAllFiles workingDirectory - : Result = + Directory.GetFiles(dirName, allFilesSearchPattern, EnumerationOptions()) + |> Array.filter (fun filePath -> not (ignoreFiles |> List.exists filePath.EndsWith))) + let deleteAllFiles workingDirectory : Result = let delete fileNames = let successes, failures = ResizeArray(), ResizeArray() @@ -47,45 +40,44 @@ module Directories = with exn -> failures.Add $"• Error deleting \"%s{fileName}\": %s{exn.Message}" - { Successes = successes |> Seq.toList |> List.rev - Failures = failures |> Seq.toList |> List.rev } + { Successes = List.ofSeq successes + Failures = List.ofSeq failures } - match getDirectoryFileNames workingDirectory None with - | Error errMsg -> Error errMsg - | Ok fileNames -> Ok (delete fileNames) + getDirectoryFileNames workingDirectory None + |> Result.map delete /// Ask the user to confirm the deletion of files in the specified directory. - let askToDeleteAllFiles dirName (printer: Printer) = + let askToDeleteAllFiles (printer: Printer) dirName = if printer.AskToBool("Delete all temporary files?", "Yes", "No") then deleteAllFiles dirName else Error "Will not delete the files." - let printDeletionResults (printer: Printer) (results: ResultMessageCollection) : unit = + let printDeletionResults (printer: Printer) (results: ResultMessages) : unit = printer.Info $"Deleted %s{String.fileLabel results.Successes.Length}." results.Successes |> List.iter printer.Debug if List.isNotEmpty results.Failures then - printer.Warning $"However, %s{String.fileLabel results.Failures.Length} could not be deleted:" + printer.Error $"%s{String.fileLabel results.Failures.Length} could not be deleted:" results.Failures |> List.iter printer.Error - let warnIfAnyFiles showMax dirName = + let warnIfAnyFiles showMax dirName : Result = match getDirectoryFileNames dirName None with | Error errMsg -> Error errMsg | Ok fileNames -> if Array.isEmpty fileNames then Ok () else - StringBuilder($"Unexpectedly found {String.fileLabel fileNames.Length} in working directory \"{dirName}\":{String.newLine}") + SB($"Unexpectedly found {String.fileLabel fileNames.Length} in working directory \"{dirName}\":{String.nl}") .AppendLine (fileNames |> Array.truncate showMax |> Array.map (sprintf "• %s") - |> String.concat String.newLine) + |> String.concat String.nl) |> fun sb -> if fileNames.Length > showMax then sb.AppendLine $"... plus {fileNames.Length - showMax} more." else sb - |> _.AppendLine("This sometimes occurs due to the same video appearing twice in playlists.") + // |> _.AppendLine("This sometimes occurs due to the same video appearing twice in playlists.") |> _.ToString() |> Error diff --git a/src/CCVTAC.Main/Orchestrator.fs b/src/CCVTAC.Main/Orchestrator.fs index 27470a9..b4b52ac 100644 --- a/src/CCVTAC.Main/Orchestrator.fs +++ b/src/CCVTAC.Main/Orchestrator.fs @@ -24,17 +24,17 @@ module Orchestrator = UpdatedSettings: UserSettings option } let summarizeInput + (printer: Printer) (categorizedInputs: CategorizedInput list) (counts: CategoryCounts) - (printer: Printer) : unit = if List.hasMultiple categorizedInputs then - let urlSummary = String.pluralizeSWithCount "URL" counts[InputCategory.Url] - let cmdSummary = String.pluralizeSWithCount "command" counts[InputCategory.Command] + let urlSummary = String.pluralizeSWithCount "URL" counts[Url] + let cmdSummary = String.pluralizeSWithCount "command" counts[Command] printer.Info <| - match counts[InputCategory.Url], counts[InputCategory.Command] with + match counts[Url], counts[Command] with | u, c when u > 0 && c > 0 -> $"Batch of %s{urlSummary} and %s{cmdSummary} entered:" | u, _ when u > 0 -> $"Batch of %s{urlSummary} entered:" | _, c when c > 0 -> $"Batch of %s{cmdSummary} entered:" @@ -43,9 +43,10 @@ module Orchestrator = for input in categorizedInputs do printer.Info $" • %s{input.Text}" - Printer.EmptyLines 1uy + printer.EmptyLine() let processUrl + (printer: Printer) (url: string) (settings: UserSettings) (resultTracker: ResultTracker) @@ -53,7 +54,6 @@ module Orchestrator = (urlInputTime: DateTime) (batchSize: int) (urlIndex: int) - (printer: Printer) : Result = match Directories.warnIfAnyFiles 10 settings.WorkingDirectory with @@ -75,7 +75,7 @@ module Orchestrator = printer.Info $"%s{mediaType.GetType().Name} URL '%s{url}' detected." history.Append(url, urlInputTime, printer) - let downloadResult = Downloader.run mediaType settings printer + let downloadResult = Downloader.run printer mediaType settings resultTracker.RegisterResult(url, downloadResult) match downloadResult with @@ -87,7 +87,7 @@ module Orchestrator = | Ok message -> printer.Debug "Download successful." if String.hasText message then printer.Info message - PostProcessor.run settings mediaType printer + PostProcessor.run printer settings mediaType let groupClause = if batchSize > 1 @@ -104,10 +104,10 @@ module Orchestrator = sprintf "%s was updated to \"%s\" for this session." settingName setting let processCommand + (printer: Printer) (command: string) (settings: UserSettings) (history: History) - (printer: Printer) : Result = let checkCommand = List.containsIgnoreCase command @@ -115,47 +115,47 @@ module Orchestrator = // Help if String.equalIgnoreCase Commands.helpCommand command then for kvp in Commands.summary do - printer.Info(kvp.Key) + printer.Info kvp.Key printer.Info $" %s{kvp.Value}" - Ok { NextAction = NextAction.Continue; UpdatedSettings = None } + Ok { NextAction = Continue; UpdatedSettings = None } // Quit elif checkCommand Commands.quitCommands then - Ok { NextAction = NextAction.QuitAtUserRequest; UpdatedSettings = None } + Ok { NextAction = QuitAtUserRequest; UpdatedSettings = None } // History elif checkCommand Commands.history then history.ShowRecent printer - Ok { NextAction = NextAction.Continue; UpdatedSettings = None } + Ok { NextAction = Continue; UpdatedSettings = None } - // Update downloader + // Update media downloader elif checkCommand Commands.updateDownloader then - Updater.run settings printer |> ignore - Ok { NextAction = NextAction.Continue; UpdatedSettings = None } + Updater.run printer settings + Ok { NextAction = Continue; UpdatedSettings = None } // Settings summary elif checkCommand Commands.settingsSummary then - Settings.printSummary settings printer None - Ok { NextAction = NextAction.Continue; UpdatedSettings = None } + Settings.toTable settings |> printer.PrintTable + Ok { NextAction = Continue; UpdatedSettings = None } // Toggle split chapters elif checkCommand Commands.splitChapterToggles then let newSettings = toggleSplitChapters settings printer.Info(summarizeToggle "Split Chapters" newSettings.SplitChapters) - Ok { NextAction = NextAction.Continue; UpdatedSettings = Some newSettings } + Ok { NextAction = Continue; UpdatedSettings = Some newSettings } // Toggle embed images elif checkCommand Commands.embedImagesToggles then let newSettings = toggleEmbedImages settings printer.Info(summarizeToggle "Embed Images" newSettings.EmbedImages) - Ok { NextAction = NextAction.Continue; UpdatedSettings = Some newSettings } + Ok { NextAction = Continue; UpdatedSettings = Some newSettings } // Toggle quiet mode elif checkCommand Commands.quietModeToggles then let newSettings = toggleQuietMode settings printer.Info(summarizeToggle "Quiet Mode" newSettings.QuietMode) printer.ShowDebug(not newSettings.QuietMode) - Ok { NextAction = NextAction.Continue; UpdatedSettings = Some newSettings } + Ok { NextAction = Continue; UpdatedSettings = Some newSettings } // Update audio formats elif command |> String.startsWithIgnoreCase Commands.updateAudioFormatPrefix then @@ -168,7 +168,7 @@ module Orchestrator = | Error err -> Error err | Ok newSettings -> printer.Info(summarizeUpdate "Audio Formats" (String.Join(", ", newSettings.AudioFormats))) - Ok { NextAction = NextAction.Continue; UpdatedSettings = Some newSettings } + Ok { NextAction = Continue; UpdatedSettings = Some newSettings } // Update audio quality elif command |> String.startsWithIgnoreCase Commands.updateAudioQualityPrefix then @@ -184,7 +184,7 @@ module Orchestrator = Error err | Ok updatedSettings -> printer.Info(summarizeUpdate "Audio Quality" (updatedSettings.AudioQuality.ToString())) - Ok { NextAction = NextAction.Continue; UpdatedSettings = Some updatedSettings } + Ok { NextAction = Continue; UpdatedSettings = Some updatedSettings } | _ -> Error $"\"%s{inputQuality}\" is an invalid quality value." @@ -199,12 +199,12 @@ module Orchestrator = /// Processes a single user request, from input to downloading and file post-processing. /// Returns the next action the application should take (e.g., continue or quit). let processBatch - (categorizedInputs: CategorizedInput list) + (printer: Printer) + (inputs: CategorizedInput list) (categoryCounts: CategoryCounts) (settings: UserSettings) (resultTracker: ResultTracker) (history: History) - (printer: Printer) : BatchResults = let inputTime = DateTime.Now @@ -223,32 +223,51 @@ module Orchestrator = let processInput category text index : Result = match category with - | Command -> processCommand text settings history printer - | Url -> processUrl text settings resultTracker history inputTime categoryCounts[Url] index printer - - let rec loop inputs settings' nextAction' index = + | Command -> processCommand printer text settings history + | Url -> processUrl printer text settings resultTracker history inputTime inputs.Length index + + let deleteLeftoverFiles dirName : Result = + match Directories.warnIfAnyFiles 10 dirName with + | Ok () -> Ok "No leftover files found." + | Error filesFoundErr -> + printer.Error filesFoundErr // Might not need this. + Directories.deleteAllFiles dirName |> function + | Ok results -> + Directories.printDeletionResults printer results + Ok "Files deleted successfully." + | Error deletionError -> + Error $"Error deleting leftover files after download: {deletionError}" + + let rec loop inputs settings' nextAction' index : NextAction * UserSettings * int = match inputs with | [] -> (nextAction', settings', index) | input :: remainingInputs when nextAction' = Continue -> - let result = processInput input.Category input.Text index - batchResults.RegisterResult(input.Text, result) - - match result with - | Error err -> - printer.Error err - if List.isNotEmpty remainingInputs then printSleep input.Category - loop remainingInputs settings' nextAction' (index + 1) - | Ok processResult -> - if List.isNotEmpty remainingInputs then printSleep input.Category - let newSettings = processResult.UpdatedSettings |> Option.defaultValue settings' - let newNextAction = processResult.NextAction - loop remainingInputs newSettings newNextAction (index + 1) + let processResult = processInput input.Category input.Text index + batchResults.RegisterResult(input.Text, processResult) + + // Deleting the files here might make debugging issues a bit tougher. + match deleteLeftoverFiles settings.WorkingDirectory with + | Error errMsg -> + printer.Error errMsg + (QuitDueToErrors, settings', index) + | Ok message -> + printer.Debug message + match processResult with + | Error err -> + printer.Error err + if List.isNotEmpty remainingInputs then printSleep input.Category + loop remainingInputs settings' nextAction' (index + 1) + | Ok processResult -> + if List.isNotEmpty remainingInputs then printSleep input.Category + let newSettings = processResult.UpdatedSettings |> Option.defaultValue settings' + let newNextAction = processResult.NextAction + loop remainingInputs newSettings newNextAction (index + 1) | _ -> (nextAction', settings', index) let (finalNextAction, finalSettings, processedCount) = - loop categorizedInputs settings Continue 1 + loop inputs settings Continue 1 if categoryCounts[Url] > 1 then printer.Info( @@ -259,9 +278,9 @@ module Orchestrator = ) batchResults.PrintBatchFailures() - if processedCount <= categorizedInputs.Length then + if processedCount <= inputs.Length then let unprocessedInputs = - categorizedInputs[processedCount-1..] + inputs[processedCount-1..] |> List.map (fun x -> $"• {x.Text}") |> String.concat String.nl printer.Warning $"Some inputs were not yet processed: {String.nl}{unprocessedInputs}" @@ -270,13 +289,13 @@ module Orchestrator = UpdatedSettings = Some finalSettings } /// Ensures the download environment is ready, then initiates the input and download process. - let start (settings: UserSettings) (printer: Printer) : unit = + let start (printer: Printer) (settings: UserSettings) : unit = // The working directory should start empty. Give the user a chance to empty it. match Directories.warnIfAnyFiles 10 settings.WorkingDirectory with | Ok () -> () | Error filesFoundErr -> printer.Error filesFoundErr - Directories.askToDeleteAllFiles settings.WorkingDirectory printer |> function + Directories.askToDeleteAllFiles printer settings.WorkingDirectory |> function | Ok results -> Directories.printDeletionResults printer results | Error deletionError -> printer.Error deletionError @@ -284,10 +303,10 @@ module Orchestrator = let results = ResultTracker printer let history = History(settings.HistoryFile, settings.HistoryDisplayCount) - let mutable nextAction = NextAction.Continue + let mutable nextAction = Continue let mutable currentSettings = settings - while nextAction = NextAction.Continue do + while nextAction = Continue do let input = printer.GetInput prompt let splitInputs = splitInputText input @@ -297,13 +316,13 @@ module Orchestrator = | _ -> let categorizedInputs = categorizeInputs splitInputs let categoryCounts = countCategories categorizedInputs - summarizeInput categorizedInputs categoryCounts printer + summarizeInput printer categorizedInputs categoryCounts - let batchResult = processBatch categorizedInputs categoryCounts currentSettings results history printer + let batchResult = processBatch printer categorizedInputs categoryCounts currentSettings results history nextAction <- batchResult.NextAction match batchResult.UpdatedSettings with - | Some s -> currentSettings <- s + | Some newSettings -> currentSettings <- newSettings | None -> () results.PrintSessionSummary() diff --git a/src/CCVTAC.Main/PostProcessing/Deleter.fs b/src/CCVTAC.Main/PostProcessing/Deleter.fs index c0a64c8..53527b5 100644 --- a/src/CCVTAC.Main/PostProcessing/Deleter.fs +++ b/src/CCVTAC.Main/PostProcessing/Deleter.fs @@ -16,7 +16,7 @@ module Deleter = try Ok (Directory.GetFiles(workingDirectory, $"*{metadata.Id}*")) with exn -> Error $"Error collecting filenames: {exn.Message}" - let private deleteAll (fileNames: string array) (printer: Printer) : unit = + let private deleteAll (printer: Printer) (fileNames: string array) : unit = fileNames |> Array.iter (fun fileName -> try @@ -27,10 +27,10 @@ module Deleter = ) let run + (printer: Printer) (taggingSetFileNames: string seq) (collectionMetadata: CollectionMetadata option) (workingDirectory: string) - (printer: Printer) : unit = let collectionFileNames = @@ -48,5 +48,5 @@ module Deleter = printer.Warning "No files to delete were found." else printer.Debug $"""Deleting {String.fileLabelWithDesc "temporary" allFileNames.Length}...""" - deleteAll allFileNames printer + deleteAll printer allFileNames printer.Info "Deleted temporary files." diff --git a/src/CCVTAC.Main/PostProcessing/ImageProcessor.fs b/src/CCVTAC.Main/PostProcessing/ImageProcessor.fs index ee62388..d8a901f 100644 --- a/src/CCVTAC.Main/PostProcessing/ImageProcessor.fs +++ b/src/CCVTAC.Main/PostProcessing/ImageProcessor.fs @@ -6,6 +6,6 @@ module ImageProcessor = let private programName = "mogrify" - let run workingDirectory printer : unit = + let run printer workingDirectory : unit = let toolSettings = workingDirectory |> ToolSettings.create $"{programName} -trim -fuzz 10%% *.jpg" - Runner.runTool toolSettings [] printer |> ignore + Runner.runTool printer toolSettings [] |> ignore diff --git a/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs b/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs index 37174e8..9ecbae5 100644 --- a/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs +++ b/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs @@ -1,8 +1,8 @@ namespace CCVTAC.Main.PostProcessing +open CCFSharpUtils open CCFSharpUtils.Text open System -open System.Text module MetadataUtilities = @@ -20,7 +20,7 @@ module MetadataUtilities = sprintf "%s/%s/%s" m d y let generateComment (v: VideoMetadata) (c: CollectionMetadata option) : string = - let sb = StringBuilder() + let sb = SB() sb.AppendLine("CCVTAC SOURCE DATA:") |> ignore sb.AppendLine $"■ Downloaded: {DateTime.Now}" |> ignore sb.AppendLine $"■ URL: %s{v.WebpageUrl}" |> ignore diff --git a/src/CCVTAC.Main/PostProcessing/Mover.fs b/src/CCVTAC.Main/PostProcessing/Mover.fs index f3674c1..5b122f1 100644 --- a/src/CCVTAC.Main/PostProcessing/Mover.fs +++ b/src/CCVTAC.Main/PostProcessing/Mover.fs @@ -107,11 +107,11 @@ module Mover = safeName.Replace(topicSuffix, String.Empty) let run + (printer: Printer) (taggingSets: TaggingSet seq) (maybeCollectionData: CollectionMetadata option) (settings: UserSettings) (overwrite: bool) - (printer: Printer) : unit = printer.Debug "Starting move..." diff --git a/src/CCVTAC.Main/PostProcessing/PostProcessing.fs b/src/CCVTAC.Main/PostProcessing/PostProcessing.fs index 103e0e9..2f041e4 100644 --- a/src/CCVTAC.Main/PostProcessing/PostProcessing.fs +++ b/src/CCVTAC.Main/PostProcessing/PostProcessing.fs @@ -48,12 +48,12 @@ module PostProcessor = | Ok ts -> Ok ts | Error msgs -> $"Error(s) creating tagging sets in working directory \"%s{dir}\"" :: msgs - |> String.concat String.newLine + |> String.concat String.nl |> Error with exn -> Error $"Error reading working files in \"{dir}\" for tagging set creation: %s{exn.Message}" - let run settings mediaType (printer: Printer) : unit = + let run (printer: Printer) settings mediaType : unit = let watch = Watch() let workingDirectory = settings.WorkingDirectory @@ -73,16 +73,16 @@ module PostProcessor = Some cm if settings.EmbedImages then - ImageProcessor.run workingDirectory printer + ImageProcessor.run printer workingDirectory - match Tagger.run settings taggingSets collectionJson mediaType printer with + match Tagger.run printer settings taggingSets collectionJson mediaType with | Ok msg -> printer.Info msg - Renamer.run settings workingDirectory printer - Mover.run taggingSets collectionJson settings true printer + Renamer.run printer settings workingDirectory + Mover.run printer taggingSets collectionJson settings true let allTaggingSetFiles = taggingSets |> List.collect allFiles - Deleter.run allTaggingSetFiles collectionJson workingDirectory printer + Deleter.run printer allTaggingSetFiles collectionJson workingDirectory match Directories.warnIfAnyFiles 20 workingDirectory with | Ok _ -> () diff --git a/src/CCVTAC.Main/PostProcessing/Renamer.fs b/src/CCVTAC.Main/PostProcessing/Renamer.fs index 2a7600e..c76460d 100644 --- a/src/CCVTAC.Main/PostProcessing/Renamer.fs +++ b/src/CCVTAC.Main/PostProcessing/Renamer.fs @@ -21,7 +21,7 @@ module Renamer = | "KC" -> NormalizationForm.FormKC | _ -> NormalizationForm.FormC - let updateTextViaPattern isQuietMode (printer: Printer) (text: SB) (renamePattern: RenamePattern) : SB = + let updateTextViaPattern (printer: Printer) isQuietMode (text: SB) (renamePattern: RenamePattern) : SB = let regex = Regex renamePattern.RegexPattern let matches = text.ToString() |> regex.Matches |> Rgx.successMatches |> Seq.rev |> Seq.toList @@ -55,7 +55,7 @@ module Renamer = text - let run userSettings workingDirectory (printer: Printer) : unit = + let run (printer: Printer) userSettings workingDirectory : unit = let watch = Watch() let workingDirInfo = DirectoryInfo workingDirectory @@ -73,7 +73,7 @@ module Renamer = let newFileName = userSettings.RenamePatterns |> List.fold - (fun acc -> updateTextViaPattern userSettings.QuietMode printer acc) + (fun acc -> updateTextViaPattern printer userSettings.QuietMode acc) (SB audioFile.Name) |> _.ToString() diff --git a/src/CCVTAC.Main/PostProcessing/Tagging/Tagger.fs b/src/CCVTAC.Main/PostProcessing/Tagging/Tagger.fs index 775a640..e1dba8d 100644 --- a/src/CCVTAC.Main/PostProcessing/Tagging/Tagger.fs +++ b/src/CCVTAC.Main/PostProcessing/Tagging/Tagger.fs @@ -24,11 +24,11 @@ module Tagger = | Null -> Error $"Deserialized JSON was null for \"%s{taggingSet.JsonFile}\"." | NonNull v -> Ok v with - | :? JsonException as exn -> Error $"%s{exn.Message}%s{String.newLine}%s{exn.StackTrace}" + | :? JsonException as exn -> Error $"%s{exn.Message}%s{String.nl}%s{exn.StackTrace}" | exn -> Error $"Error reading JSON file \"%s{taggingSet.JsonFile}\": %s{exn.Message}." /// If a video was split into sub-videos, then the original video is unneeded and should be deleted. - let private deleteSourceFile taggingSet (printer: Printer) : TaggingSet = + let private deleteSourceFile (printer: Printer) taggingSet : TaggingSet = if not (List.hasMultiple taggingSet.AudioFiles) then taggingSet else @@ -48,7 +48,7 @@ module Tagger = printer.Error $"Error deleting pre-split source file \"%s{largestFileInfo.Name}\": %s{exn.Message}" taggingSet - let private writeImageToFile (taggedFile: TaggedFile) imageFilePath (printer: Printer) = + let private writeImageToFile (printer: Printer) (taggedFile: TaggedFile) imageFilePath = if String.hasNoText imageFilePath then printer.Error "No image file path was provided, so cannot add an image to the file." else @@ -71,12 +71,12 @@ module Tagger = | _ -> None let private tagSingleFile + (printer: Printer) (settings: UserSettings) (videoData: VideoMetadata) (audioFilePath: string) (imageFilePath: string option) (collectionData: CollectionMetadata option) - (printer: Printer) : unit = let audioFileName = Path.GetFileName audioFilePath @@ -162,7 +162,7 @@ module Tagger = && settings.DoNotEmbedImageUploaders |> List.doesNotContain videoData.Uploader then printer.Debug "Embedding artwork..." - writeImageToFile taggedFile path printer + writeImageToFile printer taggedFile path else printer.Debug "Skipping artwork embedding." | None -> @@ -175,18 +175,18 @@ module Tagger = printer.Error $"Failed to save tags: ${exn.Message}" let private processTaggingSet + (printer: Printer) (settings: UserSettings) (taggingSet: TaggingSet) (collectionJson: CollectionMetadata option) (embedImages: bool) - (printer: Printer) : unit = printer.Debug $"""Found %s{String.fileLabelWithDesc "audio" taggingSet.AudioFiles.Length} with resource ID %s{taggingSet.VideoId}.""" match parseVideoJson taggingSet with | Ok videoData -> - let finalTaggingSet = deleteSourceFile taggingSet printer + let finalTaggingSet = deleteSourceFile printer taggingSet let imagePath = if embedImages && List.isNotEmpty finalTaggingSet.AudioFiles then @@ -196,18 +196,18 @@ module Tagger = for audioPath in finalTaggingSet.AudioFiles do try - tagSingleFile settings videoData audioPath imagePath collectionJson printer + tagSingleFile printer settings videoData audioPath imagePath collectionJson with exn -> printer.Error $"Error tagging file: %s{exn.Message}" | Error err -> printer.Error $"Error deserializing video metadata from \"%s{taggingSet.JsonFile}\": {err}" let run + (printer: Printer) (settings: UserSettings) (taggingSets: TaggingSet seq) (collectionJson: CollectionMetadata option) (mediaType: MediaType) - (printer: Printer) : Result = printer.Debug "Adding file tags..." @@ -216,6 +216,6 @@ module Tagger = let embedImages = settings.EmbedImages && (mediaType.IsVideo || mediaType.IsPlaylistVideo) for taggingSet in taggingSets do - processTaggingSet settings taggingSet collectionJson embedImages printer + processTaggingSet printer settings taggingSet collectionJson embedImages Ok $"Tagging done in %s{watch.ElapsedFriendly}." diff --git a/src/CCVTAC.Main/Printer.fs b/src/CCVTAC.Main/Printer.fs index bbe820d..f52fafb 100644 --- a/src/CCVTAC.Main/Printer.fs +++ b/src/CCVTAC.Main/Printer.fs @@ -96,7 +96,7 @@ type Printer(showDebug: bool) = Printer.EmptyLines appendLines - static member PrintTable(table: Table) = + member this.PrintTable(table: Table) = AnsiConsole.Write table member this.Critical(message: string, ?appendLineBreak: bool, ?prependLines: byte, ?appendLines: byte, ?processMarkup: bool) = @@ -136,15 +136,20 @@ type Printer(showDebug: bool) = this.Print(Level.Debug, message, ?appendLineBreak = appendLineBreak, ?prependLines = prependLines, ?appendLines = appendLines, ?processMarkup = processMarkup) + member this.EmptyLine() = Printer.EmptyLines 1uy + + member this.EmptyLines(count: byte) = Printer.EmptyLines count + /// Prints the requested number of blank lines. static member EmptyLines(count: byte) = - if Num.isZero count - then () - else + if Num.isPos count + then let repeats = int count - 1 if repeats < 1 then AnsiConsole.WriteLine() - else Enumerable.Repeat(String.newLine, repeats) |> String.Concat |> AnsiConsole.WriteLine + else Enumerable.Repeat(String.nl, repeats) + |> String.Concat + |> AnsiConsole.WriteLine member this.GetInput(prompt: string) : string = Printer.EmptyLines 1uy diff --git a/src/CCVTAC.Main/Program.fs b/src/CCVTAC.Main/Program.fs index 6f78df7..63cc8c9 100644 --- a/src/CCVTAC.Main/Program.fs +++ b/src/CCVTAC.Main/Program.fs @@ -26,16 +26,19 @@ module Program = let main args : int = let printer = Printer(showDebug = true) - if Array.isNotEmpty args && Array.containsIgnoreCase args[0] helpFlags then + match args with + | [||] -> printer.Info Help.helpText int ExitCodes.Success - else + | [| arg |] when helpFlags |> Array.containsIgnoreCase arg -> + printer.Info Help.helpText + int ExitCodes.Success + | [| flagArg; settingsFileArg |] -> let settingsPath = FileInfo <| - if Array.hasMultiple args && Array.containsIgnoreCase args[0] settingsFileFlags then - args[1] // Expected to be a settings file path. - else - defaultSettingsFileName + if settingsFileFlags |> Array.containsIgnoreCase flagArg + then settingsFileArg + else defaultSettingsFileName if not settingsPath.Exists then match writeDefaultFile settingsPath with @@ -51,25 +54,30 @@ module Program = printer.Error err int ExitCodes.ArgError | Ok settings -> - printSummary settings printer (Some "Settings loaded OK.") + printer.Info "Settings loaded OK." + settings |> toTable |> printer.PrintTable + printer.ShowDebug(not settings.QuietMode) // Catch Ctrl-C (SIGINT) Console.CancelKeyPress.Add(fun _ -> - printer.Warning($"{String.newLine}Quitting at user's request.") + printer.Warning($"{String.nl}Quitting at user's request.") match Directories.warnIfAnyFiles 10 settings.WorkingDirectory with | Ok () -> () | Error warnResult -> printer.Error warnResult - match Directories.askToDeleteAllFiles settings.WorkingDirectory printer with - | Error err -> printer.Error err + match Directories.askToDeleteAllFiles printer settings.WorkingDirectory with + | Error err -> printer.Error err | Ok results -> Directories.printDeletionResults printer results) try - Orchestrator.start settings printer + Orchestrator.start printer settings int ExitCodes.Success with exn -> printer.Critical $"Fatal error: %s{exn.Message}" AnsiConsole.WriteException exn printer.Info "Please help improve this tool by reporting this error and any relevant URLs at https://github.com/codeconscious/ccvtac/issues." int ExitCodes.OperationError + | _ -> + printer.Info Help.helpText + int ExitCodes.Success diff --git a/src/CCVTAC.Main/Settings/Settings.fs b/src/CCVTAC.Main/Settings/Settings.fs index 1388721..5327b2d 100644 --- a/src/CCVTAC.Main/Settings/Settings.fs +++ b/src/CCVTAC.Main/Settings/Settings.fs @@ -112,11 +112,7 @@ module Settings = ("Rename patterns", settings.RenamePatterns.Length |> simplePluralize "pattern") ] - let printSummary settings (printer: Printer) headerOpt : unit = - match headerOpt with - | Some h when String.hasText h -> printer.Info h - | _ -> () - + let toTable settings : Table = let table = Table() table.Expand() |> ignore table.Border <- TableBorder.HeavyEdge @@ -128,7 +124,7 @@ module Settings = for description, value in summarize settings do table.AddRow(description, value) |> ignore - Printer.PrintTable table + table module Validation' = open System.IO @@ -159,7 +155,7 @@ module Settings = | { AudioFormats = fmt } when not (fmt |> List.forall validAudioFormat) -> let formats = String.Join(", ", fmt) let approved = supportedAudioFormats |> String.concat ", " - Error $"Audio formats (\"%s{formats}\") include an unsupported audio format.{String.newLine}Only the following supported formats: {approved}." + Error $"Audio formats (\"%s{formats}\") include an unsupported audio format.{String.nl}Only the following supported formats: {approved}." | _ -> Ok settings diff --git a/src/CCVTAC.Main/Shared.fs b/src/CCVTAC.Main/Shared.fs index 7c6d7ff..5a26f1a 100644 --- a/src/CCVTAC.Main/Shared.fs +++ b/src/CCVTAC.Main/Shared.fs @@ -6,7 +6,7 @@ open Spectre.Console [] module Shared = - type ResultMessageCollection = { Successes: string list; Failures: string list } + type ResultMessages = { Successes: string list; Failures: string list } let sleep workingMsgFn doneMsgFn seconds : string = let rec loop remaining (ctx: StatusContext) = @@ -17,10 +17,9 @@ module Shared = AnsiConsole .Status() - .Start((workingMsgFn seconds), fun ctx -> + .Start(workingMsgFn seconds, fun ctx -> ctx.Spinner(Spinner.Known.Star) .SpinnerStyle(Style.Parse "blue") |> loop seconds) doneMsgFn seconds - diff --git a/src/CCVTAC.Tests/PostProcessing/Tagging/TagDetectionTests.fs b/src/CCVTAC.Tests/PostProcessing/Tagging/TagDetectionTests.fs index 6159704..9da59db 100644 --- a/src/CCVTAC.Tests/PostProcessing/Tagging/TagDetectionTests.fs +++ b/src/CCVTAC.Tests/PostProcessing/Tagging/TagDetectionTests.fs @@ -82,8 +82,6 @@ let emptyVideoMetadata = { Type = String.Empty } -let newLine = String.newLine - [] let ``Tag detection patterns detect metadata in video metadata`` () = let testArtist = "Test Artist Name (日本語入り)" @@ -95,7 +93,7 @@ let ``Tag detection patterns detect metadata in video metadata`` () = let videoMetadata = { emptyVideoMetadata with Title = $"{testArtist}「{testTitle}」" - Description = $"album: {testAlbum}{newLine}℗ %d{testYear}{newLine}Composed by: {testComposer}" } + Description = $"album: {testAlbum}{String.nl}℗ %d{testYear}{String.nl}Composed by: {testComposer}" } let artistPattern = { RegexPattern = "^(.+?)「(.+)」" diff --git a/src/CCVTAC.Tests/RenamerTests.fs b/src/CCVTAC.Tests/RenamerTests.fs index 5598e05..a2346f0 100644 --- a/src/CCVTAC.Tests/RenamerTests.fs +++ b/src/CCVTAC.Tests/RenamerTests.fs @@ -3,8 +3,8 @@ module RenamerTests open CCVTAC.Main open CCVTAC.Main.Settings.Settings open CCVTAC.Main.PostProcessing +open CCFSharpUtils open System -open System.Text open Xunit module UpdateTextViaPatternsTests = @@ -30,13 +30,13 @@ module UpdateTextViaPatternsTests = Summary = "Remove trailing spaces before the file extension" } ] - let fileName = StringBuilder "ARTIST「TITLE」(1923)    (字幕) [5B1rB894B1U].m4a" + let fileName = SB "ARTIST「TITLE」(1923)    (字幕) [5B1rB894B1U].m4a" let expected = "ARTIST - TITLE [1923].m4a" let actual = List.fold - (fun sb pattern -> Renamer.updateTextViaPattern true (Printer false) sb pattern) + (fun sb pattern -> Renamer.updateTextViaPattern (Printer false) true sb pattern) fileName patterns |> _.ToString()