Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions src/UniqueFileGenerator.Console/Arguments/ArgumentTypes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module ArgTypes =
let private tryParseIntInRange (floor, ceiling) text =
text
|> parseInRange (floor, ceiling)
|> Result.mapError (fun _ -> InvalidNumber (text, floor, ceiling))
|> Result.mapError (fun _ -> ParseNumberFailure (text, floor, ceiling))

type FileCount = private FileCount of int with
static member val AllowedRange = 1, Int32.MaxValue
Expand All @@ -25,9 +25,7 @@ module ArgTypes =
|> stripSeparators
|> parseInRange FileCount.AllowedRange
|> Result.mapError (fun _ ->
InvalidNumber (text,
fst FileCount.AllowedRange,
snd FileCount.AllowedRange))
ParseNumberFailure (text, fst FileCount.AllowedRange, snd FileCount.AllowedRange))
|> Result.map FileCount

member this.Value = let (FileCount count) = this in count
Expand Down Expand Up @@ -125,10 +123,11 @@ module ArgTypes =
private
{ fileCount: int
options: Options }

member x.FileCount = x.fileCount
member x.Options = x.options

static member Create(count: FileCount, options: Options) =
static member Create (count: FileCount, options: Options) =
{ fileCount = count.Value
options =
{ Prefix = options.Prefix
Expand Down
10 changes: 6 additions & 4 deletions src/UniqueFileGenerator.Console/Errors.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ module Errors =
| MalformedFlags
| UnsupportedFlags
| DuplicateFlags
| InvalidNumber of Arg: string * Floor: int * Ceiling: int
| ParseNumberFailure of Arg: string * Floor: int * Ceiling: int
| DirectoryMissing of string
| DriveSpaceConfirmationFailure
| DriveSpaceInsufficient of Needed: string * Actual: string
| UnknownError of string
| IoError of string
| CancelledByUser

let getMessage error =
match error with
Expand All @@ -22,11 +23,12 @@ module Errors =
| MalformedFlags -> "Malformed flag(s) found."
| UnsupportedFlags -> "Unsupported flag(s) found."
| DuplicateFlags -> "Duplicate option flag(s) found. Each can only be used once."
| InvalidNumber (x, f, c) ->
| ParseNumberFailure (x, f, c) ->
$"Could not parse \"%s{x}\" to an integer between %s{formatInt f} and %s{formatInt c}, inclusive."
| DirectoryMissing e -> $"Directory \"%s{e}\" was not found."
| DriveSpaceConfirmationFailure -> "Could not confirm available drive space."
| DriveSpaceInsufficient (needed, actual) ->
$"Insufficient drive space. Though %s{needed} is necessary, only %s{actual} is available."
| UnknownError e -> e
| IoError e -> $"IO error: %s{e}"
| CancelledByUser -> "Cancelled."

50 changes: 35 additions & 15 deletions src/UniqueFileGenerator.Console/Io.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ open System.IO
open System.Threading

module Io =
let private formatBytes (bytes: int64) : string =
let verifyDirectory dir =
match Directory.Exists dir with
| true -> Ok ()
| false -> Error (DirectoryMissing dir)

let private formatBytes (bytes: int64) =
let kilobyte = 1024L
let megabyte = kilobyte * 1024L
let gigabyte = megabyte * 1024L
Expand All @@ -23,11 +28,11 @@ module Io =
| _ when bytes >= kilobyte -> sprintf "%s KB" ((float bytes / float kilobyte) |> formatFloat)
| _ -> sprintf "%s bytes" (bytes |> formatInt64)


let verifyDriveSpace (args: Args) =
let bytesToKeepAvailable = 536_870_912L // 0.5 GB
let driveSpaceToKeepAvailable = 536_870_912L // 0.5 GB
let warningRatio = 0.75

let necessaryDriveSpace =
let necessarySpace =
let singleFileSize =
args.Options.Size
|> Option.defaultValue
Expand All @@ -38,6 +43,25 @@ module Io =

singleFileSize * int64 args.FileCount // Rough estimation

let confirmContinueDespiteLargeSize usableFreeSpace : bool =
let ratio = float necessarySpace / float usableFreeSpace
let isLargeRatio = ratio > warningRatio

let confirm () =
Console.Write(
sprintf "This operation requires %s, which is %.2f%% of remaining drive space. Continue? (Y/n) "
(formatBytes necessarySpace)
(ratio * 100.0))

let reply = Console.ReadLine().Trim()

[| "y"; "yes" |]
|> Array.exists (fun yesAnswer -> reply.Equals(yesAnswer, StringComparison.InvariantCultureIgnoreCase))

if isLargeRatio
then confirm ()
else true

try
let appDir = AppContext.BaseDirectory
let root = Path.GetPathRoot appDir
Expand All @@ -46,19 +70,15 @@ module Io =
| null -> Error DriveSpaceConfirmationFailure
| path ->
let driveInfo = DriveInfo path
let usableFreeSpace = driveInfo.AvailableFreeSpace - bytesToKeepAvailable
let usableFreeSpace = driveInfo.AvailableFreeSpace - driveSpaceToKeepAvailable

if necessaryDriveSpace < usableFreeSpace
then Ok <| formatBytes necessaryDriveSpace
else Error <| DriveSpaceInsufficient (formatBytes necessaryDriveSpace,
formatBytes usableFreeSpace)
if necessarySpace > usableFreeSpace
then Error (DriveSpaceInsufficient (formatBytes necessarySpace, formatBytes usableFreeSpace))
elif confirmContinueDespiteLargeSize usableFreeSpace
then Ok (formatBytes necessarySpace)
else Error CancelledByUser
with
| e -> Error <| UnknownError $"%s{e.Message}"

let verifyDirectory dir =
match Directory.Exists dir with
| true -> Ok ()
| false -> Error (DirectoryMissing dir)
| e -> Error (IoError $"%s{e.Message}")

let private createFile directory fileName (contents: string) =
try
Expand Down
8 changes: 4 additions & 4 deletions src/UniqueFileGenerator.Console/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ module Main =
do! verifyDirectory args.Options.OutputDirectory
let! spaceNeeded = verifyDriveSpace args

printLine $"This operation will use approximately %s{spaceNeeded} of drive space."
return generateFiles args
generateFiles args
return spaceNeeded
}

if Help.wasRequested rawArgs then
Help.print ()
0
else
match run rawArgs with
| Ok _ ->
printLine $"Done after %s{watch.ElapsedFriendly}"
| Ok spaceUsed ->
printLine $"Done after %s{watch.ElapsedFriendly}. Used approximately %s{spaceUsed} of drive space."
0
| Error e ->
printError <| getMessage e
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<WarnOn>3579</WarnOn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<ItemGroup>
Expand Down
6 changes: 3 additions & 3 deletions src/UniqueFileGenerator.Tests/ArgParserTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -64,21 +64,21 @@ let ``Appropriate error when invalid arg count (second pair incomplete)`` () =
[<Fact>]
let ``Appropriate error when invalid file count`` () =
let args = [| "notNumeric" |]
let expected = Error <| InvalidNumber(args[0], 1, Int32.MaxValue)
let expected = Error <| ParseNumberFailure(args[0], 1, Int32.MaxValue)
let actual = validate args
Assert.Equal(expected, actual)

[<Fact>]
let ``Appropriate error when negative file count`` () =
let args = [| "-1" |]
let expected = Error <| InvalidNumber(args[0], 1, Int32.MaxValue)
let expected = Error <| ParseNumberFailure(args[0], 1, Int32.MaxValue)
let actual = validate args
Assert.Equal(expected, actual)

[<Fact>]
let ``Appropriate error when zero file count`` () =
let args = [| "0" |]
let expected = Error <| InvalidNumber(args[0], 1, Int32.MaxValue)
let expected = Error <| ParseNumberFailure(args[0], 1, Int32.MaxValue)
let actual = validate args
Assert.Equal(expected, actual)

Expand Down