예제 #1
0
        private static async Task <string[]> AutoCompleteDirectories(FdbPath path, IFdbDatabase db, TextWriter log, CancellationToken ct)
        {
            var parent = await db.ReadAsync(tr => BasicCommands.TryOpenCurrentDirectoryAsync(tr, path), ct);

            if (parent == null)
            {
                return(null);
            }

            var paths = await db.ReadAsync(tr => parent.ListAsync(tr), ct);

            return(paths.Select(p => p.Name).ToArray());
        }
예제 #2
0
        private static async Task <string[]> AutoCompleteDirectories(string prm, IFdbDatabase db, TextWriter log, CancellationToken ct)
        {
            var path   = ParsePath(prm);
            var parent = await BasicCommands.TryOpenCurrentDirectoryAsync(path, db, ct).ConfigureAwait(false);

            if (parent == null)
            {
                return(null);
            }

            var names = await parent.ListAsync(db, ct).ConfigureAwait(false);

            return(names.ToArray());
        }
        public static async Task ChangeDirectoryLayer(string[] path, string layer, IVarTuple extras, IFdbDatabase db, TextWriter log, CancellationToken ct)
        {
            var dir = await BasicCommands.TryOpenCurrentDirectoryAsync(path, db, ct);

            if (dir == null)
            {
                Program.Error(log, $"# Directory {String.Join("/", path)} does not exist anymore");
            }
            else
            {
                dir = await db.ReadWriteAsync((tr) => dir.ChangeLayerAsync(tr, Slice.FromString(layer)), ct);

                Program.Success(log, $"# Directory {String.Join("/", path)} layer changed to {dir.Layer:P}");
            }
        }
예제 #4
0
        public static async Task ChangeDirectoryLayer(string[] path, string layer, IFdbTuple extras, IFdbDatabase db, TextWriter log, CancellationToken ct)
        {
            var dir = await BasicCommands.TryOpenCurrentDirectoryAsync(path, db, ct);

            if (dir == null)
            {
                log.WriteLine("# Directory {0} does not exist anymore", String.Join("/", path));
            }
            else
            {
                dir = await db.ReadWriteAsync((tr) => dir.ChangeLayerAsync(tr, Slice.FromString(layer)), ct);

                log.WriteLine("# Directory {0} layer changed to {1}", String.Join("/", path), dir.Layer.ToAsciiOrHexaString());
            }
        }
 public static Task ChangeDirectoryLayer(FdbPath path, string layer, IVarTuple extras, IFdbDatabase db, TextWriter log, CancellationToken ct)
 {
     return(db.WriteAsync(async tr =>
     {
         var dir = await BasicCommands.TryOpenCurrentDirectoryAsync(tr, path);
         if (dir == null)
         {
             Program.Error(log, $"# Directory {path} does not exist anymore");
         }
         else
         {
             dir = await dir.ChangeLayerAsync(tr, layer);
             Program.Success(log, $"# Directory {path} layer changed to {dir.Layer}");
         }
     }, ct));
 }
        public static async Task ShowDirectoryLayer(string[] path, IVarTuple extras, IFdbDatabase db, TextWriter log, CancellationToken ct)
        {
            var dir = await BasicCommands.TryOpenCurrentDirectoryAsync(path, db, ct);

            if (dir == null)
            {
                Program.Error(log, $"# Directory {string.Join("/", path)} does not exist anymore");
            }
            else
            {
                if (dir.Layer == FdbDirectoryPartition.LayerId)
                {
                    log.WriteLine($"# Directory {string.Join("/", path)} is a partition");
                }
                else if (dir.Layer.IsPresent)
                {
                    log.WriteLine($"# Directory {string.Join("/", path)} has layer {dir.Layer:P}");
                }
                else
                {
                    log.WriteLine($"# Directory {string.Join("/", path)} does not have a layer defined");
                }
            }
        }
예제 #7
0
        public static async Task ShowDirectoryLayer(string[] path, IFdbTuple extras, IFdbDatabase db, TextWriter log, CancellationToken ct)
        {
            var dir = await BasicCommands.TryOpenCurrentDirectoryAsync(path, db, ct);

            if (dir == null)
            {
                log.WriteLine("# Directory {0} does not exist anymore", String.Join("/", path));
            }
            else
            {
                if (dir.Layer == FdbDirectoryPartition.LayerId)
                {
                    log.WriteLine("# Directory {0} is a partition", String.Join("/", path));
                }
                else if (dir.Layer.IsPresent)
                {
                    log.WriteLine("# Directory {0} has layer {1}", String.Join("/", path), dir.Layer.ToAsciiOrHexaString());
                }
                else
                {
                    log.WriteLine("# Directory {0} does not have a layer defined", String.Join("/", path));
                }
            }
        }
예제 #8
0
        private static async Task MainAsync(string[] args, CancellationToken cancel)
        {
            #region Options Parsing...

            string clusterFile = null;
            var    partition   = FdbPath.Root;
            bool   showHelp    = false;
            int    timeout     = 30;
            int    maxRetries  = 10;
            string execCommand = null;

            var opts = new OptionSet()
            {
                {
                    "c|C|connfile=",
                    "The path of a file containing the connection string for the FoundationDB cluster.",
                    v => clusterFile = v
                },
                {
                    "p|partition=",
                    "The name of the database partition to open.",
                    v => partition = FdbPath.Parse(v.Trim())
                },
                {
                    "t|timeout=",
                    "Default timeout (in seconds) for failed transactions.",
                    (int v) => timeout = v
                },
                {
                    "r|retries=",
                    "Default max retry count for failed transactions.",
                    (int v) => maxRetries = v
                },
                {
                    "exec=",
                    "Execute this command, and exits immediately.",
                    v => execCommand = v
                },
                {
                    "h|help",
                    "Show this help and exit.",
                    v => showHelp = v != null
                }
            };

            var extra = opts.Parse(args);

            if (showHelp)
            {
                //TODO!
                opts.WriteOptionDescriptions(Console.Out);
                return;
            }

            string startCommand = null;
            if (!string.IsNullOrEmpty(execCommand))
            {
                startCommand = execCommand;
            }
            else if (extra.Count > 0)
            {             // the remainder of the command line will be the first command to execute
                startCommand = String.Join(" ", extra);
            }

            #endregion

            bool stop = false;
            Db = null;
            try
            {
                var cnxOptions = new FdbConnectionOptions
                {
                    ClusterFile = clusterFile,
                    Root        = partition
                };
                Db = await ChangeDatabase(cnxOptions, cancel);

                Db.DefaultTimeout    = Math.Max(0, timeout) * 1000;
                Db.DefaultRetryLimit = Math.Max(0, maxRetries);

                StdOut("Using API v" + Fdb.ApiVersion + " (max " + Fdb.GetMaxApiVersion() + ")", ConsoleColor.Gray);
                StdOut("Cluster file: " + (clusterFile ?? "<default>"), ConsoleColor.Gray);
                StdOut("");
                StdOut("FoundationDB Shell menu:");
                StdOut("\tcd\tChange the current directory");
                StdOut("\tdir\tList the sub-directories the current directory");
                StdOut("\tshow\tShow the content of the current directory");
                StdOut("\ttree\tShow all the directories under the current directory");
                StdOut("\tsampling\tDisplay statistics on random shards from the database");
                StdOut("\tcoordinators\tShow the current coordinators for the cluster");
                //StdOut("\thelp\tShow all the commands");
                StdOut("\tquit\tQuit");
                StdOut("");

                try
                {
                    var cf = await Fdb.System.GetCoordinatorsAsync(Db, cancel);

                    Description = cf.Description;
                    StdOut("Ready...", ConsoleColor.DarkGreen);
                }
                catch (Exception e)
                {
                    StdErr("Failed to get coordinators state from cluster: " + e.Message, ConsoleColor.DarkRed);
                    Description = "???";
                }

                StdOut("");

                var le = new LineEditor("FDBShell");

                string[] cmds = new string[]
                {
                    "cd",
                    "clear",
                    "coordinators",
                    "count",
                    "dir",
                    "dump",
                    "exit",
                    "gc",
                    "help",
                    "layer",
                    "map",
                    "mem",
                    "mkdir",
                    "mv",
                    "partition",
                    "pwd",
                    "quit",
                    "ren",
                    "rmdir",
                    "sampling",
                    "shards",
                    "show",
                    "status",
                    "topology",
                    "tree",
                    "version",
                    "wide",
                };

                le.AutoCompleteEvent = (txt, pos) =>
                {
                    string[] res;
                    int      p = txt.IndexOf(' ');
                    if (p > 0)
                    {
                        string cmd = txt.Substring(0, p);
                        string arg = txt.Substring(p + 1).Trim();

                        if (cmd == "cd" || cmd == "rmdir")
                        {                         // handle completion for directories
                            // txt: "cd foo" => prefix = "foo"
                            // txt: "cd foobar/b" => prefix = "b"

                            bool   hasLeadingSlash = arg.EndsWith("/");
                            var    path            = FdbPath.Parse(hasLeadingSlash ? (arg + "!") : arg);
                            var    parent          = path.Count > 1 ? path.GetParent() : path.IsAbsolute ? FdbPath.Root : FdbPath.Empty;
                            string search          = hasLeadingSlash ? "" : path.Name;

                            var subdirs = RunAsyncCommand((db, log, ct) => AutoCompleteDirectories(CombinePath(CurrentDirectoryPath, parent.ToString()), db, log, ct), cancel).GetAwaiter().GetResult();

                            if (!subdirs.HasValue || subdirs.Value == null)
                            {
                                return(new LineEditor.Completion(txt, null));
                            }

                            res = subdirs.Value
                                  .Where(s => s.StartsWith(search, StringComparison.Ordinal))
                                  .Select(s => (cmd + " " + parent[s]).Substring(txt.Length))
                                  .ToArray();

                            if (res.Length == 1 && res[0] == string.Empty)
                            {                             // someone was at "cd /Foo/Bar", pressed TAB again, and there is no other match
                                // => we interpret it as "want to go in the sub-folder

                                res = new[] { "/" };                                 // add a "slash"
                            }

                            return(new LineEditor.Completion(txt, res));
                        }

                        // unknown command
                        return(new LineEditor.Completion(txt, null));
                    }

                    // list of commands
                    res = cmds
                          .Where(cmd => cmd.StartsWith(txt, StringComparison.OrdinalIgnoreCase))
                          .Select(cmd => cmd.Substring(txt.Length))
                          .ToArray();
                    return(new LineEditor.Completion(txt, res));
                };
                le.TabAtStartCompletes = true;

                string prompt = null;

                void UpdatePrompt(FdbPath path)
                {
                    prompt = $"[fdb:{Description} {path.ToString()}]# ";
                }
                le.PromptColor = ConsoleColor.Cyan;
                UpdatePrompt(CurrentDirectoryPath);

                while (!stop)
                {
                    string s;
                    if (startCommand != null)
                    {
                        s            = startCommand;
                        startCommand = null;
                    }
                    else
                    {
                        s = startCommand ?? le.Edit(prompt, "");
                    }

                    if (s == null)
                    {
                        break;
                    }

                    //TODO: we need a tokenizer that recognizes binary keys, tuples, escaped paths, etc...
                    var    tokens = Tokenize(s);
                    string cmd    = tokens.Count > 0 ? tokens.Get <string>(0) : string.Empty;
                    var    extras = tokens.Count > 1 ? tokens.Substring(1) : STuple.Empty;

                    var trimmedCommand = cmd.Trim().ToLowerInvariant();
                    try
                    {
                        switch (trimmedCommand)
                        {
                        case "":
                        {
                            continue;
                        }

                        case "log":
                        {
                            string prm = PopParam(ref extras);
                            LogCommand(prm, extras, Console.Out);
                            break;
                        }

                        case "version":
                        {
                            await VersionCommand(extras, clusterFile, Console.Out, cancel);

                            break;
                        }

                        case "tree":
                        {
                            string prm  = PopParam(ref extras);
                            var    path = CombinePath(CurrentDirectoryPath, prm);
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Tree(path, extras, db, log, ct), cancel);

                            break;
                        }

                        case "map":
                        {
                            string prm  = PopParam(ref extras);
                            var    path = CombinePath(CurrentDirectoryPath, prm);
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Map(path, extras, db, log, ct), cancel);

                            break;
                        }

                        case "dir":
                        case "ls":
                        {
                            string prm  = PopParam(ref extras);
                            var    path = CombinePath(CurrentDirectoryPath, prm);
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Dir(path, extras, BasicCommands.DirectoryBrowseOptions.Default, db, log, ct), cancel);

                            break;
                        }

                        case "ll":
                        {
                            string prm  = PopParam(ref extras);
                            var    path = CombinePath(CurrentDirectoryPath, prm);
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Dir(path, extras, BasicCommands.DirectoryBrowseOptions.ShowCount, db, log, ct), cancel);

                            break;
                        }

                        case "count":
                        {
                            string prm  = PopParam(ref extras);
                            var    path = CombinePath(CurrentDirectoryPath, prm);
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Count(path, extras, db, log, ct), cancel);

                            break;
                        }

                        case "show":
                        case "top":
                        {
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Show(CurrentDirectoryPath, extras, false, db, log, ct), cancel);

                            break;
                        }

                        case "last":
                        {
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Show(CurrentDirectoryPath, extras, true, db, log, ct), cancel);

                            break;
                        }

                        case "dump":
                        {
                            string output = PopParam(ref extras);
                            if (string.IsNullOrEmpty(output))
                            {
                                StdErr("You must specify a target file path.", ConsoleColor.Red);
                                break;
                            }
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Dump(CurrentDirectoryPath, output, extras, db, log, ct), cancel);

                            break;
                        }

                        case "cd":
                        case "pwd":
                        {
                            string prm = PopParam(ref extras);
                            if (!string.IsNullOrEmpty(prm))
                            {
                                var newPath = CombinePath(CurrentDirectoryPath, prm);
                                var res     = await RunAsyncCommand(
                                    (db, log, ct) => db.ReadAsync(tr => BasicCommands.TryOpenCurrentDirectoryAsync(tr, newPath), ct),
                                    cancel
                                    );

                                if (res.Failed)
                                {
                                    StdErr($"# Failed to open Directory {newPath}: {res.Error.Message}", ConsoleColor.Red);
                                    Console.Beep();
                                }
                                else if (res.Value == null)
                                {
                                    StdOut($"# Directory {newPath} does not exist!", ConsoleColor.Red);
                                    Console.Beep();
                                }
                                else
                                {
                                    CurrentDirectoryPath = newPath;
                                    UpdatePrompt(CurrentDirectoryPath);
                                }
                            }
                            else
                            {
                                var res = await RunAsyncCommand(
                                    (db, log, ct) => db.ReadAsync(tr => BasicCommands.TryOpenCurrentDirectoryAsync(tr, CurrentDirectoryPath), ct),
                                    cancel
                                    );

                                if (res.Failed)
                                {
                                    StdErr($"# Failed to query Directory {Program.CurrentDirectoryPath}: {res.Error.Message}", ConsoleColor.Red);
                                }
                                else if (res.Value == null)
                                {
                                    StdOut($"# Directory {Program.CurrentDirectoryPath} does not exist anymore");
                                }
                            }

                            break;
                        }

                        case "mkdir":
                        case "md":
                        {                                 // "mkdir DIRECTORYNAME"
                            string prm = PopParam(ref extras);
                            if (!string.IsNullOrEmpty(prm))
                            {
                                var path = CombinePath(CurrentDirectoryPath, prm);
                                await RunAsyncCommand((db, log, ct) => BasicCommands.CreateDirectory(path, extras, db, log, ct), cancel);
                            }

                            break;
                        }

                        case "rmdir":
                        {                                 // "rmdir DIRECTORYNAME"
                            string prm = PopParam(ref extras);
                            if (!string.IsNullOrEmpty(prm))
                            {
                                var path = CombinePath(CurrentDirectoryPath, prm);
                                await RunAsyncCommand((db, log, ct) => BasicCommands.RemoveDirectory(path, extras, db, log, ct), cancel);
                            }

                            break;
                        }

                        case "mv":
                        case "ren":
                        {                                 // "mv SOURCE DESTINATION"
                            string prm     = PopParam(ref extras);
                            var    srcPath = CombinePath(CurrentDirectoryPath, prm);
                            var    dstPath = CombinePath(CurrentDirectoryPath, extras.Get <string>(0));
                            await RunAsyncCommand((db, log, ct) => BasicCommands.MoveDirectory(srcPath, dstPath, extras.Substring(1), db, log, ct), cancel);

                            break;
                        }

                        case "get":
                        {                                 // "get KEY"
                            if (extras.Count == 0)
                            {
                                StdErr("You must specify a key to read.", ConsoleColor.Red);
                                break;
                            }

                            await RunAsyncCommand((db, log, ct) => BasicCommands.Get(CurrentDirectoryPath, extras, db, log, ct), cancel);

                            break;
                        }

                        case "clear":
                        {                                 // "clear KEY"
                            if (extras.Count == 0)
                            {
                                StdErr("You must specify a key to clear.", ConsoleColor.Red);
                                break;
                            }

                            await RunAsyncCommand((db, log, ct) => BasicCommands.Clear(CurrentDirectoryPath, extras, db, log, ct), cancel);

                            break;
                        }

                        case "clearrange":
                        {                                 // "clear *" or "clear FROM TO"
                            if (extras.Count == 0)
                            {
                                StdErr("You must specify either '*', a prefix, or a key range.", ConsoleColor.Red);
                                break;
                            }

                            await RunAsyncCommand((db, log, ct) => BasicCommands.ClearRange(CurrentDirectoryPath, extras, db, log, ct), cancel);

                            break;
                        }

                        case "layer":
                        {
                            string prm = PopParam(ref extras);
                            if (string.IsNullOrEmpty(prm))
                            {                                     // displays the layer id of the current folder
                                await RunAsyncCommand((db, log, ct) => BasicCommands.ShowDirectoryLayer(CurrentDirectoryPath, extras, db, log, ct), cancel);
                            }
                            else
                            {                                     // change the layer id of the current folder
                                prm = prm.Trim();
                                // double or single quotes can be used to escape the value
                                if (prm.Length >= 2 && (prm.StartsWith("'") && prm.EndsWith("'")) || (prm.StartsWith("\"") && prm.EndsWith("\"")))
                                {
                                    prm = prm.Substring(1, prm.Length - 2);
                                }

                                await RunAsyncCommand((db, log, ct) => BasicCommands.ChangeDirectoryLayer(CurrentDirectoryPath, prm, extras, db, log, ct), cancel);
                            }

                            break;
                        }

                        case "mkpart":
                        {                                 // "mkpart PARTITIONNAME"
                            string prm = PopParam(ref extras);
                            if (!string.IsNullOrEmpty(prm))
                            {
                                var path = CombinePath(CurrentDirectoryPath, prm);
                                await RunAsyncCommand((db, log, ct) => BasicCommands.CreateDirectory(path, STuple.Create(FdbDirectoryPartition.LayerId).Concat(extras), db, log, ct), cancel);
                            }

                            break;
                        }

                        case "topology":
                        {
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Topology(null, extras, db, log, ct), cancel);

                            break;
                        }

                        case "shards":
                        {
                            string prm  = PopParam(ref extras);
                            var    path = CombinePath(CurrentDirectoryPath, prm);
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Shards(path, extras, db, log, ct), cancel);

                            break;
                        }

                        case "sampling":
                        {
                            string prm  = PopParam(ref extras);
                            var    path = CombinePath(CurrentDirectoryPath, prm);
                            await RunAsyncCommand((db, log, ct) => BasicCommands.Sampling(path, extras, db, log, ct), cancel);

                            break;
                        }

                        case "coordinators":
                        {
                            await RunAsyncCommand((db, log, ct) => CoordinatorsCommand(db, log, ct), cancel);

                            break;
                        }

                        case "partition":
                        {
                            string prm = PopParam(ref extras);
                            if (string.IsNullOrEmpty(prm))
                            {
                                StdOut($"# Current partition is {partition}");
                                //TODO: browse existing partitions ?
                                break;
                            }

                            var          newPartition = FdbPath.Parse(prm.Trim());
                            IFdbDatabase newDb        = null;
                            try
                            {
                                var options = new FdbConnectionOptions
                                {
                                    ClusterFile = clusterFile,
                                    Root        = newPartition
                                };
                                newDb = await ChangeDatabase(options, cancel);
                            }
                            catch (Exception)
                            {
                                newDb?.Dispose();
                                newDb = null;
                                throw;
                            }
                            finally
                            {
                                if (newDb != null)
                                {
                                    if (Db != null)
                                    {
                                        Db.Dispose();
                                        Db = null;
                                    }

                                    Db        = newDb;
                                    partition = newPartition;
                                    StdOut($"# Changed partition to {partition}");
                                }
                            }

                            break;
                        }

                        case "q":
                        case "x":
                        case "quit":
                        case "exit":
                        case "bye":
                        {
                            stop = true;
                            break;
                        }

                        case "gc":
                        {
                            long before = GC.GetTotalMemory(false);
                            Console.Write("Collecting garbage...");
                            GC.Collect();
                            GC.WaitForPendingFinalizers();
                            GC.Collect();
                            StdOut(" Done");
                            long after = GC.GetTotalMemory(false);
                            StdOut("- before = " + before.ToString("N0"));
                            StdOut("- after  = " + after.ToString("N0"));
                            StdOut("- delta  = " + (before - after).ToString("N0"));
                            break;
                        }

                        case "mem":
                        {
                            StdOut("Memory usage:");
                            StdOut("- Managed Mem  : " + GC.GetTotalMemory(false).ToString("N0"));
                            //TODO: how do we get these values on Linux/Mac?
#if !NETCOREAPP
                            StdOut("- Working Set  : " + PerfCounters.WorkingSet.NextValue().ToString("N0") + " (peak " + PerfCounters.WorkingSetPeak.NextValue().ToString("N0") + ")");
                            StdOut("- Virtual Bytes: " + PerfCounters.VirtualBytes.NextValue().ToString("N0") + " (peak " + PerfCounters.VirtualBytesPeak.NextValue().ToString("N0") + ")");
                            StdOut("- Private Bytes: " + PerfCounters.PrivateBytes.NextValue().ToString("N0"));
                            StdOut("- BytesInAlHeap: " + PerfCounters.ClrBytesInAllHeaps.NextValue().ToString("N0"));
#endif
                            break;
                        }

                        case "wide":
                        {
                            try
                            {
                                Console.WindowWidth = 160;
                            }
                            catch (Exception e)
                            {
                                StdErr("Failed to change console width: " + e.Message, ConsoleColor.DarkRed);
                            }

                            break;
                        }

                        case "status":
                        case "wtf":
                        {
                            var result = await RunAsyncCommand((_, log, ct) => FdbCliCommands.RunFdbCliCommand("status details", null, clusterFile, log, ct), cancel);

                            if (result.Failed)
                            {
                                break;
                            }
                            if (result.Value.ExitCode != 0)
                            {
                                StdErr($"# fdbcli exited with code {result.Value.ExitCode}", ConsoleColor.DarkRed);
                                StdOut("> StdErr:", ConsoleColor.DarkGray);
                                StdOut(result.Value.StdErr);
                                StdOut("> StdOut:", ConsoleColor.DarkGray);
                            }

                            StdOut(result.Value.StdOut);
                            break;
                        }

                        default:
                        {
                            StdErr($"Unknown command : '{trimmedCommand}'", ConsoleColor.Red);
                            break;
                        }
                        }
                    }
                    catch (Exception e)
                    {
                        StdErr($"Failed to execute command '{trimmedCommand}': " + e.Message, ConsoleColor.Red);
#if DEBUG
                        StdErr(e.ToString(), ConsoleColor.DarkRed);
#endif
                    }

                    if (!string.IsNullOrEmpty(execCommand))
                    {                     // only run one command, and then exit
                        break;
                    }
                }
            }
            finally
            {
                Program.Db?.Dispose();
            }
        }
예제 #9
0
        private static async Task MainAsync(string[] args, CancellationToken cancel)
        {
            #region Options Parsing...

            string clusterFile = null;
            var    dbName      = "DB";
            var    partition   = new string[0];
            bool   showHelp    = false;
            int    timeout     = 30;
            int    maxRetries  = 10;
            string execCommand = null;

            var opts = new OptionSet()
            {
                {
                    "c|connfile=",
                    "The path of a file containing the connection string for the FoundationDB cluster.",
                    v => clusterFile = v
                },
                {
                    "p|partition=",
                    "The name of the database partition to open.",
                    v => partition = v.Trim().Split('/')
                },
                {
                    "t|timeout=",
                    "Default timeout (in seconds) for failed transactions.",
                    (int v) => timeout = v
                },
                {
                    "r|retries=",
                    "Default max retry count for failed transactions.",
                    (int v) => maxRetries = v
                },
                {
                    "exec=",
                    "Execute this command, and exits immediately.",
                    v => execCommand = v
                },
                {
                    "h|help",
                    "Show this help and exit.",
                    v => showHelp = v != null
                }
            };

            var extra = opts.Parse(args);

            if (showHelp)
            {
                //TODO!
                opts.WriteOptionDescriptions(Console.Out);
                return;
            }

            string startCommand = null;
            if (!string.IsNullOrEmpty(execCommand))
            {
                startCommand = execCommand;
            }
            else if (extra.Count > 0)
            {             // the remainder of the command line will be the first command to execute
                startCommand = String.Join(" ", extra);
            }

            #endregion

            bool stop = false;
            Db = null;
            try
            {
                Db = await ChangeDatabase(clusterFile, dbName, partition, cancel);

                Db.DefaultTimeout    = Math.Max(0, timeout) * 1000;
                Db.DefaultRetryLimit = Math.Max(0, maxRetries);

                Console.WriteLine("Using API v" + Fdb.ApiVersion + " (max " + Fdb.GetMaxApiVersion() + ")");
                Console.WriteLine("Cluster file: " + (clusterFile ?? "<default>"));
                Console.WriteLine();
                Console.WriteLine("FoundationDB Shell menu:");
                Console.WriteLine("\tdir\tShow the content of the current directory");
                Console.WriteLine("\ttree\tShow all the directories under the current directory");
                Console.WriteLine("\tsampling\tDisplay statistics on random shards from the database");
                Console.WriteLine("\tcoordinators\tShow the current coordinators for the cluster");
                Console.WriteLine("\tmem\tShow memory usage statistics");
                Console.WriteLine("\tgc\tTrigger garbage collection");
                Console.WriteLine("\tquit\tQuit");

                Console.WriteLine("Ready...");


                var le = new LineEditor("FDBShell");

                string[] cmds = new string[]
                {
                    "cd",
                    "coordinators",
                    "count",
                    "dir",
                    "exit",
                    "gc",
                    "help",
                    "layer",
                    "map",
                    "mem",
                    "mkdir",
                    "mv",
                    "partition",
                    "pwd",
                    "quit",
                    "ren",
                    "rmdir",
                    "sampling",
                    "shards",
                    "show",
                    "status",
                    "topology",
                    "tree",
                    "version",
                    "wide",
                };

                le.AutoCompleteEvent = (txt, pos) =>
                {
                    string[] res;
                    int      p = txt.IndexOf(' ');
                    if (p > 0)
                    {
                        string cmd = txt.Substring(0, p);
                        string arg = txt.Substring(p + 1);

                        if (cmd == "cd")
                        {                         // handle completion for directories
                            // txt: "cd foo" => prefix = "foo"
                            // txt: "cd foobar/b" => prefix = "b"

                            string path   = CurrentDirectoryPath;
                            string prefix = "";
                            string search = arg;
                            p = arg.LastIndexOf('/');
                            if (p > 0)
                            {
                                path   = Path.Combine(path, arg.Substring(0, p));
                                search = arg.Substring(p + 1);
                                prefix = arg.Substring(0, p + 1);
                            }

                            var subdirs = RunAsyncCommand((db, log, ct) => AutoCompleteDirectories(path, db, log, ct), cancel).GetAwaiter().GetResult();
                            if (!subdirs.HasValue || subdirs.Value == null)
                            {
                                return(new LineEditor.Completion(txt, null));
                            }

                            res = subdirs.Value
                                  .Where(s => s.StartsWith(search, StringComparison.Ordinal))
                                  .Select(s => (cmd + " " + prefix + s).Substring(txt.Length))
                                  .ToArray();
                            return(new LineEditor.Completion(txt, res));
                        }

                        // unknown command
                        return(new LineEditor.Completion(txt, null));
                    }

                    // list of commands
                    res = cmds
                          .Where(cmd => cmd.StartsWith(txt, StringComparison.OrdinalIgnoreCase))
                          .Select(cmd => cmd.Substring(txt.Length))
                          .ToArray();
                    return(new LineEditor.Completion(txt, res));
                };
                le.TabAtStartCompletes = true;

                string          prompt       = null;
                Action <string> updatePrompt = (path) => { prompt = String.Format("fdb:{0}> ", path); };
                updatePrompt(CurrentDirectoryPath);

                while (!stop)
                {
                    string s = startCommand != null ? startCommand : le.Edit(prompt, "");
                    startCommand = null;

                    if (s == null)
                    {
                        break;
                    }

                    var    tokens = s.Trim().Split(new [] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                    string cmd    = tokens.Length > 0 ? tokens[0] : String.Empty;
                    string prm    = tokens.Length > 1 ? tokens[1] : String.Empty;
                    var    extras = tokens.Length > 2 ? FdbTuple.CreateRange <string>(tokens.Skip(2)) : FdbTuple.Empty;

                    var trimmedCommand = cmd.Trim().ToLowerInvariant();
                    switch (trimmedCommand)
                    {
                    case "":
                    {
                        continue;
                    }

                    case "log":
                    {
                        LogCommand(prm, Console.Out);

                        break;
                    }

                    case "version":
                    {
                        await VersionCommand(prm, clusterFile, Console.Out, cancel);

                        break;
                    }

                    case "tree":
                    {
                        var path = ParsePath(CombinePath(CurrentDirectoryPath, prm));
                        await RunAsyncCommand((db, log, ct) => BasicCommands.Tree(path, extras, db, log, ct), cancel);

                        break;
                    }

                    case "map":
                    {
                        var path = ParsePath(CombinePath(CurrentDirectoryPath, prm));
                        await RunAsyncCommand((db, log, ct) => BasicCommands.Map(path, extras, db, log, ct), cancel);

                        break;
                    }

                    case "dir":
                    case "ls":
                    {
                        var path = ParsePath(CombinePath(CurrentDirectoryPath, prm));
                        await RunAsyncCommand((db, log, ct) => BasicCommands.Dir(path, extras, BasicCommands.DirectoryBrowseOptions.Default, db, log, ct), cancel);

                        break;
                    }

                    case "ll":
                    {
                        var path = ParsePath(CombinePath(CurrentDirectoryPath, prm));
                        await RunAsyncCommand((db, log, ct) => BasicCommands.Dir(path, extras, BasicCommands.DirectoryBrowseOptions.ShowCount, db, log, ct), cancel);

                        break;
                    }

                    case "count":
                    {
                        var path = ParsePath(CombinePath(CurrentDirectoryPath, prm));
                        await RunAsyncCommand((db, log, ct) => BasicCommands.Count(path, extras, db, log, ct), cancel);

                        break;
                    }

                    case "show":
                    case "top":
                    {
                        var path = ParsePath(CurrentDirectoryPath);
                        await RunAsyncCommand((db, log, ct) => BasicCommands.Show(path, extras, false, db, log, ct), cancel);

                        break;
                    }

                    case "last":
                    {
                        var path = ParsePath(CurrentDirectoryPath);
                        await RunAsyncCommand((db, log, ct) => BasicCommands.Show(path, extras, true, db, log, ct), cancel);

                        break;
                    }

                    case "cd":
                    case "pwd":
                    {
                        if (!string.IsNullOrEmpty(prm))
                        {
                            var newPath = CombinePath(CurrentDirectoryPath, prm);
                            var res     = await RunAsyncCommand((db, log, ct) => BasicCommands.TryOpenCurrentDirectoryAsync(ParsePath(newPath), db, ct), cancel);

                            if (res == null)
                            {
                                Console.WriteLine("# Directory {0} does not exist!", newPath);
                                Console.Beep();
                            }
                            else
                            {
                                CurrentDirectoryPath = newPath;
                                updatePrompt(CurrentDirectoryPath);
                            }
                        }
                        else
                        {
                            var res = await RunAsyncCommand((db, log, ct) => BasicCommands.TryOpenCurrentDirectoryAsync(ParsePath(CurrentDirectoryPath), db, ct), cancel);

                            if (res.GetValueOrDefault() == null)
                            {
                                Console.WriteLine("# Directory {0} does not exist anymore", CurrentDirectoryPath);
                            }
                            else
                            {
                                Console.WriteLine("# {0}", res);
                            }
                        }
                        break;
                    }

                    case "mkdir":
                    case "md":
                    {                             // "mkdir DIRECTORYNAME"
                        if (!string.IsNullOrEmpty(prm))
                        {
                            var path = ParsePath(CombinePath(CurrentDirectoryPath, prm));
                            await RunAsyncCommand((db, log, ct) => BasicCommands.CreateDirectory(path, extras, db, log, ct), cancel);
                        }
                        break;
                    }

                    case "rmdir":
                    {                             // "rmdir DIRECTORYNAME"
                        if (!string.IsNullOrEmpty(prm))
                        {
                            var path = ParsePath(CombinePath(CurrentDirectoryPath, prm));
                            await RunAsyncCommand((db, log, ct) => BasicCommands.RemoveDirectory(path, extras, db, log, ct), cancel);
                        }
                        break;
                    }

                    case "mv":
                    case "ren":
                    {                             // "mv SOURCE DESTINATION"
                        var srcPath = ParsePath(CombinePath(CurrentDirectoryPath, prm));
                        var dstPath = ParsePath(CombinePath(CurrentDirectoryPath, extras.Get <string>(0)));
                        await RunAsyncCommand((db, log, ct) => BasicCommands.MoveDirectory(srcPath, dstPath, extras.Substring(1), db, log, ct), cancel);

                        break;
                    }

                    case "layer":
                    {
                        if (string.IsNullOrEmpty(prm))
                        {                                 // displays the layer id of the current folder
                            var path = ParsePath(CurrentDirectoryPath);
                            await RunAsyncCommand((db, log, ct) => BasicCommands.ShowDirectoryLayer(path, extras, db, log, ct), cancel);
                        }
                        else
                        {                                 // change the layer id of the current folder
                            prm = prm.Trim();
                            // double or single quotes can be used to escape the value
                            if (prm.Length >= 2 && (prm.StartsWith("'") && prm.EndsWith("'")) || (prm.StartsWith("\"") && prm.EndsWith("\"")))
                            {
                                prm = prm.Substring(1, prm.Length - 2);
                            }
                            var path = ParsePath(CurrentDirectoryPath);
                            await RunAsyncCommand((db, log, ct) => BasicCommands.ChangeDirectoryLayer(path, prm, extras, db, log, ct), cancel);
                        }
                        break;
                    }

                    case "mkpart":
                    {                             // "mkpart PARTITIONNAME"
                        if (!string.IsNullOrEmpty(prm))
                        {
                            var path = ParsePath(CombinePath(CurrentDirectoryPath, prm));
                            await RunAsyncCommand((db, log, ct) => BasicCommands.CreateDirectory(path, FdbTuple.Create(FdbDirectoryPartition.LayerId).Concat(extras), db, log, ct), cancel);
                        }

                        break;
                    }

                    case "topology":
                    {
                        await RunAsyncCommand((db, log, ct) => BasicCommands.Topology(null, extras, db, log, ct), cancel);

                        break;
                    }

                    case "shards":
                    {
                        var path = ParsePath(CombinePath(CurrentDirectoryPath, prm));
                        await RunAsyncCommand((db, log, ct) => BasicCommands.Shards(path, extras, db, log, ct), cancel);

                        break;
                    }

                    case "sampling":
                    {
                        var path = ParsePath(CombinePath(CurrentDirectoryPath, prm));
                        await RunAsyncCommand((db, log, ct) => BasicCommands.Sampling(path, extras, db, log, ct), cancel);

                        break;
                    }

                    case "coordinators":
                    {
                        await RunAsyncCommand((db, log, ct) => CoordinatorsCommand(db, log, ct), cancel);

                        break;
                    }

                    case "partition":
                    {
                        if (string.IsNullOrEmpty(prm))
                        {
                            Console.WriteLine("# Current partition is {0}", String.Join("/", partition));
                            //TODO: browse existing partitions ?
                            break;
                        }

                        var          newPartition = prm.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
                        IFdbDatabase newDb        = null;
                        try
                        {
                            newDb = await ChangeDatabase(clusterFile, dbName, newPartition, cancel);
                        }
                        catch (Exception)
                        {
                            if (newDb != null)
                            {
                                newDb.Dispose();
                            }
                            newDb = null;
                            throw;
                        }
                        finally
                        {
                            if (newDb != null)
                            {
                                if (Db != null)
                                {
                                    Db.Dispose(); Db = null;
                                }
                                Db        = newDb;
                                partition = newPartition;
                                Console.WriteLine("# Changed partition to {0}", partition);
                            }
                        }
                        break;
                    }

                    case "q":
                    case "x":
                    case "quit":
                    case "exit":
                    case "bye":
                    {
                        stop = true;
                        break;
                    }

                    case "gc":
                    {
                        long before = GC.GetTotalMemory(false);
                        Console.Write("Collecting garbage...");
                        GC.Collect();
                        GC.WaitForPendingFinalizers();
                        GC.Collect();
                        Console.WriteLine(" Done");
                        long after = GC.GetTotalMemory(false);
                        Console.WriteLine("- before = " + before.ToString("N0"));
                        Console.WriteLine("- after  = " + after.ToString("N0"));
                        Console.WriteLine("- delta  = " + (before - after).ToString("N0"));
                        break;
                    }

                    case "mem":
                    {
                        Console.WriteLine("Memory usage:");
                        Console.WriteLine("- Working Set  : " + PerfCounters.WorkingSet.NextValue().ToString("N0") + " (peak " + PerfCounters.WorkingSetPeak.NextValue().ToString("N0") + ")");
                        Console.WriteLine("- Virtual Bytes: " + PerfCounters.VirtualBytes.NextValue().ToString("N0") + " (peak " + PerfCounters.VirtualBytesPeak.NextValue().ToString("N0") + ")");
                        Console.WriteLine("- Private Bytes: " + PerfCounters.PrivateBytes.NextValue().ToString("N0"));
                        Console.WriteLine("- Managed Mem  : " + GC.GetTotalMemory(false).ToString("N0"));
                        Console.WriteLine("- BytesInAlHeap: " + PerfCounters.ClrBytesInAllHeaps.NextValue().ToString("N0"));
                        break;
                    }

                    case "wide":
                    {
                        Console.WindowWidth = 160;
                        break;
                    }

                    case "status":
                    case "wtf":
                    {
                        var result = await RunAsyncCommand((_, log, ct) => FdbCliCommands.RunFdbCliCommand("status details", null, clusterFile, log, ct), cancel);

                        if (result.HasFailed)
                        {
                            break;
                        }
                        if (result.Value.ExitCode != 0)
                        {
                            Console.WriteLine("# fdbcli exited with code {0}", result.Value.ExitCode);
                            Console.WriteLine("> StdErr:");
                            Console.WriteLine(result.Value.StdErr);
                            Console.WriteLine("> StdOut:");
                        }
                        Console.WriteLine(result.Value.StdOut);
                        break;
                    }

                    default:
                    {
                        Console.WriteLine(string.Format("Unknown command : '{0}'", trimmedCommand));
                        break;
                    }
                    }

                    if (!string.IsNullOrEmpty(execCommand))
                    {                     // only run one command, and then exit
                        break;
                    }
                }
            }
            finally
            {
                if (Db != null)
                {
                    Db.Dispose();
                }
            }
        }