#!/usr/bin/env -S dotnet fsi #r "nuget: MongoDB.Driver" #r "nuget: MongoDB.Driver.GridFS" #r "nuget: Mondocks.Net" #r "nuget: Spectre.Console" open System open System.IO open Spectre.Console open MongoDB.Bson open MongoDB.Driver open MongoDB.Driver.GridFS open Mondocks.Types open Mondocks.Queries let args = (fsi.CommandLineArgs) // here we also start by parsing the cli args let restoreTo = args |> Seq.tryPick (fun arg -> if arg.Contains("--restoreDir=") then Some arg else None) |> Option.map (fun arg -> arg.Replace("--restoreDir=", "")) let dbArg = args |> Seq.tryPick (fun arg -> if arg.Contains("--database=") then Some arg else None) |> Option.map (fun arg -> arg.Replace("--database=", "")) let restoreDir = match restoreTo with | None -> Directory.GetCurrentDirectory() | Some dir -> Path.GetFullPath(dir) let dburl = match dbArg with | None -> "mongodb://localhost:27017" | Some url -> url if not (Directory.Exists(restoreDir)) then raise (exn "Couldn't set Current Directory") let dir = DirectoryInfo(restoreDir) // we'll use the same backup entry we used before, // the only difference is that this includes the _id field type BackupEntry = { _id: ObjectId; directory: string; backupAt: DateTime; filenames: string array; entryType: string } type EntryType = | Attempt | Success member this.AsString() = match this with | Attempt -> "Attempt" | Success -> "Success" static member FromString(value: string) = match value with | "Attempt" -> Attempt | "Success" -> Success | _ -> failwith "Unknown value" let client = MongoClient(dburl) let backupsdb = client.GetDatabase("backups") let bucket = GridFSBucket(backupsdb) let findBackupsCmd = // here we do a find query using an anonymous object // and we'll filter by successful backup entries find "backups" { filter {| entryType = EntryType.Success.AsString() |} } let backups = backupsdb.RunCommand<FindResult<BackupEntry>>(JsonCommand findBackupsCmd) Console.Clear() AnsiConsole.MarkupLine("[bold]Found the following backups[/]") let tbl = Table() tbl .AddColumn("Directory") .AddColumn("Backup Date") .AddColumn("Files") .AddColumn("Entry Type") let found = backups.cursor.firstBatch |> Seq.sortByDescending(fun entry -> entry.backupAt) found |> Seq.iteri(fun index entry -> let date = $"{entry.backupAt.ToShortDateString()} - {entry.backupAt.ToShortTimeString()}" let files = $"{entry.filenames |> Array.length}" tbl.AddRow([| $"{index + 1} - {entry.directory}"; date; files; entry.entryType |]) |> ignore ) AnsiConsole.Render(tbl) let prompt = let txt = TextPrompt<int>("Select the desired backup") found |> Seq.iteri(fun index backup -> txt.AddChoice<int>(index + 1) |> ignore) txt .DefaultValue(1) // let's try to also validate that the answer // is within range of our existing backups .Validator <- fun value -> match value with | value when value <= 0 || value >= (found |> Seq.length) -> ValidationResult.Error("The selected backup was not found") | result -> ValidationResult.Success() txt let response = (AnsiConsole .Prompt<int>(prompt)) - 1 let selected = backups.cursor.firstBatch |> Seq.item response let filenames = selected.filenames AnsiConsole .Progress() .Columns([| new SpinnerColumn() new PercentageColumn() new ProgressBarColumn() new TaskDescriptionColumn() |]) .Start(fun ctx -> let settings = ProgressTaskSettings() settings.MaxValue <- filenames |> Seq.length |> float let task = ctx.AddTask($"Restoring files to %s{dir.FullName}", settings) for filename in filenames do // for every entry in our backup we create a file stream // thankfully MongoDB.Driver.GridFS includes a way to download // the files directly into a stream, so the process is quite seamless use filestr = File.Create(Path.Combine(dir.FullName, filename)) bucket.DownloadToStreamByName(filename, filestr) task.Increment(1.0) task.StopTask() ) AnsiConsole.WriteLine($"Restored: %i{filenames |> Seq.length} files")