summaryrefslogtreecommitdiff
path: root/bin/bbackupquery
diff options
context:
space:
mode:
authorChris Wilson <chris+github@qwirx.com>2010-09-29 17:59:09 +0000
committerChris Wilson <chris+github@qwirx.com>2010-09-29 17:59:09 +0000
commite9af7f8259aa9fb8440100d5c25fb755f71879d0 (patch)
treef80048b929ce720331f446528f400b6416234857 /bin/bbackupquery
parent28a1c334a7320ca57ebd85d0c30cb228df5bb938 (diff)
Implement basic readline/editline completion of remote file and directory
names, object IDs, and command options.
Diffstat (limited to 'bin/bbackupquery')
-rw-r--r--bin/bbackupquery/BackupQueries.cpp554
-rw-r--r--bin/bbackupquery/BackupQueries.h84
-rw-r--r--bin/bbackupquery/bbackupquery.cpp119
3 files changed, 589 insertions, 168 deletions
diff --git a/bin/bbackupquery/BackupQueries.cpp b/bin/bbackupquery/BackupQueries.cpp
index 002a9ec4..356f8d61 100644
--- a/bin/bbackupquery/BackupQueries.cpp
+++ b/bin/bbackupquery/BackupQueries.cpp
@@ -24,6 +24,24 @@
#include <dirent.h>
#endif
+#ifdef HAVE_LIBREADLINE
+ #ifdef HAVE_READLINE_READLINE_H
+ #include <readline/readline.h>
+ #elif defined(HAVE_EDITLINE_READLINE_H)
+ #include <editline/readline.h>
+ #elif defined(HAVE_READLINE_H)
+ #include <readline.h>
+ #endif
+#endif
+
+#ifdef HAVE_READLINE_HISTORY
+ #ifdef HAVE_READLINE_HISTORY_H
+ #include <readline/history.h>
+ #elif defined(HAVE_HISTORY_H)
+ #include <history.h>
+ #endif
+#endif
+
#include <cstring>
#include <limits>
#include <iostream>
@@ -63,25 +81,380 @@
#define COMPARE_RETURN_ERROR 3
#define COMMAND_RETURN_ERROR 4
+#define COMPLETION_FUNCTION(name, code) \
+std::vector<std::string> Complete ## name( \
+ BackupQueries::ParsedCommand& rCommand, \
+ const std::string& prefix, \
+ BackupProtocolClient& rProtocol, const Configuration& rConfig, \
+ BackupQueries& rQueries) \
+{ \
+ std::vector<std::string> completions; \
+ \
+ try \
+ { \
+ code \
+ } \
+ catch(std::exception &e) \
+ { \
+ BOX_TRACE("Failed to complete " << prefix << ": " << e.what()); \
+ } \
+ catch(...) \
+ { \
+ BOX_TRACE("Failed to complete " << prefix << ": " \
+ "unknown error"); \
+ } \
+ \
+ return completions; \
+}
+
+#define DELEGATE_COMPLETION(name) \
+ completions = Complete ## name(rCommand, prefix, rProtocol, rConfig, \
+ rQueries);
+
+COMPLETION_FUNCTION(None,)
+
+#ifdef HAVE_RL_FILENAME_COMPLETION_FUNCTION
+ #define RL_FILENAME_COMPLETION_FUNCTION rl_filename_completion_function
+#else
+ #define RL_FILENAME_COMPLETION_FUNCTION filename_completion_function
+#endif
+
+COMPLETION_FUNCTION(Default,
+ while (const char *match = RL_FILENAME_COMPLETION_FUNCTION(prefix.c_str(), 0))
+ {
+ completions.push_back(match);
+ }
+)
+
+COMPLETION_FUNCTION(Command,
+ int len = prefix.length();
+
+ for(int i = 0; commands[i].name != NULL; i++)
+ {
+ if(::strncmp(commands[i].name, prefix.c_str(), len) == 0)
+ {
+ completions.push_back(commands[i].name);
+ }
+ }
+)
+
+void CompleteOptionsInternal(const std::string& prefix,
+ BackupQueries::ParsedCommand& rCommand,
+ std::vector<std::string>& completions)
+{
+ std::string availableOptions = rCommand.pSpec->opts;
+
+ for(std::string::iterator
+ opt = availableOptions.begin();
+ opt != availableOptions.end(); opt++)
+ {
+ if(rCommand.mOptions.find(*opt) == std::string::npos)
+ {
+ if(prefix == "")
+ {
+ // complete with possible option strings
+ completions.push_back(std::string("-") + *opt);
+ }
+ else
+ {
+ // complete with possible additional options
+ completions.push_back(prefix + *opt);
+ }
+ }
+ }
+}
+
+COMPLETION_FUNCTION(Options,
+ CompleteOptionsInternal(prefix, rCommand, completions);
+)
+
+std::string EncodeFileName(const std::string &rUnEncodedName)
+{
+#ifdef WIN32
+ std::string encodedName;
+ if(!ConvertConsoleToUtf8(rUnEncodedName, encodedName))
+ {
+ return std::string();
+ }
+ return encodedName;
+#else
+ return rUnEncodedName;
+#endif
+}
+
+#define LIST_OPTION_ALLOWOLD 'o'
+#define LIST_OPTION_ALLOWDELETED 'd'
+
+int16_t GetExcludeFlags(BackupQueries::ParsedCommand& rCommand)
+{
+ int16_t excludeFlags = 0;
+
+ if (rCommand.mOptions.find(LIST_OPTION_ALLOWOLD) == std::string::npos)
+ {
+ excludeFlags |= BackupProtocolClientListDirectory::Flags_OldVersion;
+ }
+
+ if (rCommand.mOptions.find(LIST_OPTION_ALLOWDELETED) == std::string::npos)
+ {
+ excludeFlags |= BackupProtocolClientListDirectory::Flags_Deleted;
+ }
+
+ return excludeFlags;
+}
+
+std::vector<std::string> CompleteRemoteFileOrDirectory(
+ BackupQueries::ParsedCommand& rCommand,
+ const std::string& prefix, BackupProtocolClient& rProtocol,
+ BackupQueries& rQueries, int16_t includeFlags)
+{
+ std::vector<std::string> completions;
+
+ // default to using the current directory
+ int64_t listDirId = rQueries.GetCurrentDirectoryID();
+ std::string searchPrefix;
+ std::string listDir = prefix;
+
+ if(rCommand.mArgCount == rCommand.mCmdElements.size())
+ {
+ // completing an empty name, from the current directory
+ // nothing to change
+ }
+ else
+ {
+ // completing a partially-completed subdirectory name
+ searchPrefix = prefix;
+ listDir = "";
+
+ // do we need to list a subdirectory to complete?
+ size_t lastSlash = searchPrefix.rfind('/');
+ if(lastSlash == std::string::npos)
+ {
+ // no slashes, so the whole name is the prefix
+ // nothing to change
+ }
+ else
+ {
+ // listing a partially-completed subdirectory name
+ listDir = searchPrefix.substr(0, lastSlash);
+
+ listDirId = rQueries.FindDirectoryObjectID(listDir,
+ rCommand.mOptions.find(LIST_OPTION_ALLOWOLD)
+ != std::string::npos,
+ rCommand.mOptions.find(LIST_OPTION_ALLOWDELETED)
+ != std::string::npos);
+
+ if(listDirId == 0)
+ {
+ // no matches for subdir to list,
+ // return empty-handed.
+ return completions;
+ }
+
+ // matched, and updated listDir and listDirId already
+ searchPrefix = searchPrefix.substr(lastSlash + 1);
+ }
+ }
+
+ // Always include directories, because they contain files.
+ // We will append a slash later for each directory if we're
+ // actually looking for files.
+ //
+ // If we're looking for directories, then only list directories.
+
+ bool completeFiles = includeFlags &
+ BackupProtocolClientListDirectory::Flags_File;
+ bool completeDirs = includeFlags &
+ BackupProtocolClientListDirectory::Flags_Dir;
+ int16_t listFlags = 0;
+
+ if(completeFiles)
+ {
+ listFlags = BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING;
+ }
+ else if(completeDirs)
+ {
+ listFlags = BackupProtocolClientListDirectory::Flags_Dir;
+ }
+
+ rProtocol.QueryListDirectory(listDirId,
+ listFlags, GetExcludeFlags(rCommand),
+ false /* no attributes */);
+
+ // Retrieve the directory from the stream following
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(rProtocol.ReceiveStream());
+ dir.ReadFromStream(*dirstream, rProtocol.GetTimeout());
+
+ // Then... display everything
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next()) != 0)
+ {
+ BackupStoreFilenameClear clear(en->GetName());
+ std::string name = clear.GetClearFilename().c_str();
+ if(name.compare(0, searchPrefix.length(), searchPrefix) == 0)
+ {
+ if(en->IsDir() &&
+ (includeFlags & BackupProtocolClientListDirectory::Flags_Dir) == 0)
+ {
+ // Was looking for a file, but this is a
+ // directory, so append a slash to the name
+ name += "/";
+ }
+
+ if(listDir == "")
+ {
+ completions.push_back(name);
+ }
+ else
+ {
+ completions.push_back(listDir + "/" + name);
+ }
+ }
+ }
+
+ return completions;
+}
+
+COMPLETION_FUNCTION(RemoteDir,
+ completions = CompleteRemoteFileOrDirectory(rCommand, prefix,
+ rProtocol, rQueries,
+ BackupProtocolClientListDirectory::Flags_Dir);
+)
+
+COMPLETION_FUNCTION(RemoteFile,
+ completions = CompleteRemoteFileOrDirectory(rCommand, prefix,
+ rProtocol, rQueries,
+ BackupProtocolClientListDirectory::Flags_File);
+)
+
+COMPLETION_FUNCTION(LocalDir,
+ DELEGATE_COMPLETION(Default);
+)
+
+COMPLETION_FUNCTION(LocalFile,
+ DELEGATE_COMPLETION(Default);
+)
+
+COMPLETION_FUNCTION(LocationName,
+ const Configuration &locations(rConfig.GetSubConfiguration(
+ "BackupLocations"));
+
+ std::vector<std::string> locNames =
+ locations.GetSubConfigurationNames();
+
+ for(std::vector<std::string>::iterator
+ pLocName = locNames.begin();
+ pLocName != locNames.end();
+ pLocName++)
+ {
+ if(pLocName->compare(0, pLocName->length(), prefix) == 0)
+ {
+ completions.push_back(*pLocName);
+ }
+ }
+)
+
+COMPLETION_FUNCTION(RemoteFileIdInCurrentDir,
+ int64_t listDirId = rQueries.GetCurrentDirectoryID();
+ int16_t excludeFlags = GetExcludeFlags(rCommand);
+
+ rProtocol.QueryListDirectory(
+ listDirId,
+ BackupProtocolClientListDirectory::Flags_File,
+ excludeFlags, false /* no attributes */);
+
+ // Retrieve the directory from the stream following
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(rProtocol.ReceiveStream());
+ dir.ReadFromStream(*dirstream, rProtocol.GetTimeout());
+
+ // Then... compare each item
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next()) != 0)
+ {
+ std::ostringstream hexId;
+ hexId << std::hex << en->GetObjectID();
+ if(hexId.str().compare(0, prefix.length(), prefix) == 0)
+ {
+ completions.push_back(hexId.str());
+ }
+ }
+)
+
+// TODO implement completion of hex IDs up to the maximum according to Usage
+COMPLETION_FUNCTION(RemoteId,)
+
+COMPLETION_FUNCTION(GetFileOrId,
+ if(rCommand.mOptions.find('i') != std::string::npos)
+ {
+ DELEGATE_COMPLETION(RemoteFileIdInCurrentDir);
+ }
+ else
+ {
+ DELEGATE_COMPLETION(RemoteFile);
+ }
+)
+
+COMPLETION_FUNCTION(CompareLocationOrRemoteDir,
+ if(rCommand.mOptions.find('l') != std::string::npos)
+ {
+ DELEGATE_COMPLETION(LocationName);
+ }
+ else
+ {
+ DELEGATE_COMPLETION(RemoteDir);
+ }
+)
+
+COMPLETION_FUNCTION(CompareNoneOrLocalDir,
+ if(rCommand.mOptions.find('l') != std::string::npos)
+ {
+ // no completions
+ DELEGATE_COMPLETION(None);
+ }
+ else
+ {
+ DELEGATE_COMPLETION(LocalDir);
+ }
+)
+
+COMPLETION_FUNCTION(RestoreRemoteDirOrId,
+ if(rCommand.mOptions.find('i') != std::string::npos)
+ {
+ DELEGATE_COMPLETION(RemoteId);
+ }
+ else
+ {
+ DELEGATE_COMPLETION(RemoteDir);
+ }
+)
+
// Data about commands
QueryCommandSpecification commands[] =
{
- { "quit", "", Command_Quit },
- { "exit", "", Command_Quit },
- { "list", "rodIFtTash", Command_List },
- { "pwd", "", Command_pwd },
- { "cd", "od", Command_cd },
- { "lcd", "", Command_lcd },
- { "sh", "", Command_sh },
- { "getobject", "", Command_GetObject },
- { "get", "i", Command_Get },
- { "compare", "alcqAEQ", Command_Compare },
- { "restore", "drif", Command_Restore },
- { "help", "", Command_Help },
- { "usage", "m", Command_Usage },
- { "undelete", "", Command_Undelete },
- { "delete", "", Command_Delete },
- { NULL, NULL, Command_Unknown }
+ { "quit", "", Command_Quit, {} },
+ { "exit", "", Command_Quit, {} },
+ { "list", "rodIFtTash", Command_List, {CompleteRemoteDir} },
+ { "pwd", "", Command_pwd, {} },
+ { "cd", "od", Command_cd, {CompleteRemoteDir} },
+ { "lcd", "", Command_lcd, {CompleteLocalDir} },
+ { "sh", "", Command_sh, {CompleteDefault} },
+ { "getobject", "", Command_GetObject,
+ {CompleteRemoteId, CompleteLocalDir} },
+ { "get", "i", Command_Get,
+ {CompleteGetFileOrId, CompleteLocalDir} },
+ { "compare", "alcqAEQ", Command_Compare,
+ {CompleteCompareLocationOrRemoteDir, CompleteCompareNoneOrLocalDir} },
+ { "restore", "drif", Command_Restore,
+ {CompleteRestoreRemoteDirOrId, CompleteLocalDir} },
+ { "help", "", Command_Help, {} },
+ { "usage", "m", Command_Usage, {} },
+ { "undelete", "", Command_Undelete,
+ {CompleteGetFileOrId} },
+ { "delete", "", Command_Delete, {CompleteGetFileOrId} },
+ { NULL, NULL, Command_Unknown, {} }
};
const char *alias[] = {"ls", 0};
@@ -124,25 +497,37 @@ BackupQueries::~BackupQueries()
{
}
-BackupQueries::ParsedCommand
-BackupQueries::ParseCommand(const std::string& Command, bool isFromCommandLine)
+BackupQueries::ParsedCommand::ParsedCommand(const std::string& Command,
+ bool isFromCommandLine)
+: mInOptions(false),
+ mFailed(false),
+ pSpec(NULL),
+ mArgCount(0)
{
- ParsedCommand parsed;
- parsed.completeCommand = Command;
+ mCompleteCommand = Command;
// is the command a shell command?
if(Command[0] == 's' && Command[1] == 'h' && Command[2] == ' ' && Command[3] != '\0')
{
// Yes, run shell command
- parsed.cmdElements[0] = "sh";
- parsed.cmdElements[1] = Command.c_str() + 3;
- return parsed;
+ for(int i = 0; commands[i].type != Command_Unknown; i++)
+ {
+ if(commands[i].type == Command_sh)
+ {
+ pSpec = &(commands[i]);
+ break;
+ }
+ }
+
+ mCmdElements[0] = "sh";
+ mCmdElements[1] = Command.c_str() + 3;
+ return;
}
// split command into components
const char *c = Command.c_str();
bool inQuoted = false;
- bool inOptions = false;
+ mInOptions = false;
std::string s;
while(*c != 0)
@@ -150,10 +535,22 @@ BackupQueries::ParseCommand(const std::string& Command, bool isFromCommandLine)
// Terminating char?
if(*c == ((inQuoted)?'"':' '))
{
- if(!s.empty()) parsed.cmdElements.push_back(s);
+ if(!s.empty())
+ {
+ mCmdElements.push_back(s);
+
+ // Because we just parsed a space, if this
+ // wasn't an option word, then we're now
+ // completing the next (or first) arg
+ if(!mInOptions)
+ {
+ mArgCount++;
+ }
+ }
+
s.resize(0);
inQuoted = false;
- inOptions = false;
+ mInOptions = false;
}
else
{
@@ -165,14 +562,14 @@ BackupQueries::ParseCommand(const std::string& Command, bool isFromCommandLine)
// Start of options?
else if(s.empty() && *c == '-')
{
- inOptions = true;
+ mInOptions = true;
}
else
{
- if(inOptions)
+ if(mInOptions)
{
// Option char
- parsed.options += *c;
+ mOptions += *c;
}
else
{
@@ -187,40 +584,69 @@ BackupQueries::ParseCommand(const std::string& Command, bool isFromCommandLine)
if(!s.empty())
{
- parsed.cmdElements.push_back(s);
+ mCmdElements.push_back(s);
+ }
+
+ // Work out which command it is...
+ int cmd = 0;
+ while(mCmdElements.size() > 0 && commands[cmd].name != 0 &&
+ mCmdElements[0] != commands[cmd].name)
+ {
+ cmd++;
}
+ if(mCmdElements.size() > 0 && commands[cmd].name == 0)
+ {
+ // Check for aliases
+ int a;
+ for(a = 0; alias[a] != 0; ++a)
+ {
+ if(mCmdElements[0] == alias[a])
+ {
+ // Found an alias
+ cmd = aliasIs[a];
+ break;
+ }
+ }
+ }
+
+ if(mCmdElements.size() == 0 || commands[cmd].name == 0)
+ {
+ mFailed = true;
+ return;
+ }
+
+ pSpec = &(commands[cmd]);
+
#ifdef WIN32
if(isFromCommandLine)
{
std::string converted;
- if(!ConvertEncoding(parsed.completeCommand, CP_ACP, converted,
+ if(!ConvertEncoding(mCompleteCommand, CP_ACP, converted,
GetConsoleCP()))
{
BOX_ERROR("Failed to convert encoding");
- parsed.failed = true;
+ failed = true;
}
- parsed.completeCommand = converted;
+ mCompleteCommand = converted;
for(std::vector<std::string>::iterator
- i = parsed.cmdElements.begin();
- i != parsed.cmdElements.end(); i++)
+ i = mCmdElements.begin();
+ i != mCmdElements.end(); i++)
{
if(!ConvertEncoding(*i, CP_ACP, converted,
GetConsoleCP()))
{
BOX_ERROR("Failed to convert encoding");
- parsed.failed = true;
+ mFailed = true;
}
*i = converted;
}
}
#endif
-
- return parsed;
}
// --------------------------------------------------------------------------
@@ -234,16 +660,17 @@ BackupQueries::ParseCommand(const std::string& Command, bool isFromCommandLine)
void BackupQueries::DoCommand(ParsedCommand& rCommand)
{
// Check...
- if(rCommand.cmdElements.size() < 1)
+ if(rCommand.mCmdElements.size() < 1)
{
// blank command
return;
}
- if(rCommand.cmdElements[0] == "sh" && rCommand.cmdElements.size() == 2)
+ if(rCommand.pSpec->type == Command_sh &&
+ rCommand.mCmdElements.size() == 2)
{
// Yes, run shell command
- int result = ::system(rCommand.cmdElements[1].c_str());
+ int result = ::system(rCommand.mCmdElements[1].c_str());
if(result != 0)
{
BOX_WARNING("System command returned error code " <<
@@ -253,39 +680,16 @@ void BackupQueries::DoCommand(ParsedCommand& rCommand)
return;
}
- // Work out which command it is...
- int cmd = 0;
- while(commands[cmd].name != 0 &&
- rCommand.cmdElements[0] != commands[cmd].name)
- {
- cmd++;
- }
-
- if(commands[cmd].name == 0)
- {
- // Check for aliases
- int a;
- for(a = 0; alias[a] != 0; ++a)
- {
- if(rCommand.cmdElements[0] == alias[a])
- {
- // Found an alias
- cmd = aliasIs[a];
- break;
- }
- }
- }
-
- if(commands[cmd].name == 0 || commands[cmd].type == Command_Unknown)
+ if(rCommand.pSpec->type == Command_Unknown)
{
// No such command
- BOX_ERROR("Unrecognised command: " << rCommand.cmdElements[0]);
+ BOX_ERROR("Unrecognised command: " << rCommand.mCmdElements[0]);
return;
}
// Arguments
- std::vector<std::string> args(rCommand.cmdElements.begin() + 1,
- rCommand.cmdElements.end());
+ std::vector<std::string> args(rCommand.mCmdElements.begin() + 1,
+ rCommand.mCmdElements.end());
// Set up options
bool opts[256];
@@ -293,14 +697,14 @@ void BackupQueries::DoCommand(ParsedCommand& rCommand)
// BLOCK
{
// options
- const char *c = rCommand.options.c_str();
+ const char *c = rCommand.mOptions.c_str();
while(*c != 0)
{
// Valid option?
- if(::strchr(commands[cmd].opts, *c) == NULL)
+ if(::strchr(rCommand.pSpec->opts, *c) == NULL)
{
BOX_ERROR("Invalid option '" << *c << "' for "
- "command " << commands[cmd].name);
+ "command " << rCommand.pSpec->name);
return;
}
opts[(int)*c] = true;
@@ -308,14 +712,14 @@ void BackupQueries::DoCommand(ParsedCommand& rCommand)
}
}
- if(commands[cmd].type != Command_Quit)
+ if(rCommand.pSpec->type != Command_Quit)
{
// If not a quit command, set the return code to zero
SetReturnCode(ReturnCode::Command_OK);
}
// Handle command
- switch(commands[cmd].type)
+ switch(rCommand.pSpec->type)
{
case Command_Quit:
mQuitNow = true;
@@ -378,7 +782,7 @@ void BackupQueries::DoCommand(ParsedCommand& rCommand)
break;
default:
- BOX_ERROR("Unknown command: " << rCommand.cmdElements[0]);
+ BOX_ERROR("Unknown command: " << rCommand.mCmdElements[0]);
break;
}
}
@@ -395,8 +799,6 @@ void BackupQueries::DoCommand(ParsedCommand& rCommand)
void BackupQueries::CommandList(const std::vector<std::string> &args, const bool *opts)
{
#define LIST_OPTION_RECURSIVE 'r'
- #define LIST_OPTION_ALLOWOLD 'o'
- #define LIST_OPTION_ALLOWDELETED 'd'
#define LIST_OPTION_NOOBJECTID 'I'
#define LIST_OPTION_NOFLAGS 'F'
#define LIST_OPTION_TIMES_LOCAL 't'
diff --git a/bin/bbackupquery/BackupQueries.h b/bin/bbackupquery/BackupQueries.h
index 5d562103..7bc756c0 100644
--- a/bin/bbackupquery/BackupQueries.h
+++ b/bin/bbackupquery/BackupQueries.h
@@ -40,31 +40,7 @@ typedef enum
}
CommandType;
-typedef enum
-{
- Complete_End = 0,
- Complete_RemoteDir,
- Complete_RemoteFile,
- Complete_LocalDir,
- Complete_LocalFile,
- Complete_LocationName,
- Complete_RemoteIdInCurrentDir,
-}
-CompletionType;
-
-typedef struct
-{
- const char* name;
- const char* opts;
- CommandType type;
-}
-QueryCommandSpecification;
-
-// Data about commands
-extern QueryCommandSpecification commands[];
-
-extern const char *alias[];
-extern const int aliasIs[];
+struct QueryCommandSpecification;
// --------------------------------------------------------------------------
//
@@ -86,17 +62,21 @@ private:
public:
struct ParsedCommand
{
- std::vector<std::string> cmdElements;
- std::string options;
- std::string completeCommand;
- bool failed;
- ParsedCommand() : failed(false) { }
+ std::vector<std::string> mCmdElements;
+ std::string mOptions;
+ std::string mCompleteCommand;
+ bool mInOptions, mFailed;
+ QueryCommandSpecification* pSpec;
+ // mArgCount is the same as mCmdElements.size() for a complete
+ // command, but if the command line ends in a space,
+ // e.g. during readline parsing, it can be one greater,
+ // to indicate that we should complete the next item instead.
+ size_t mArgCount;
+ ParsedCommand(const std::string& Command,
+ bool isFromCommandLine);
};
- ParsedCommand ParseCommand(const std::string& Command,
- bool isFromCommandLine);
void DoCommand(ParsedCommand& rCommand);
- CompletionType* GetCompletionTypes(ParsedCommand& rCommand);
// Ready to stop?
bool Stop() {return mQuitNow;}
@@ -374,17 +354,19 @@ public:
} Type;
};
-private:
-
- // Utility functions
+ // Were private, but needed by completion functions:
+ int64_t GetCurrentDirectoryID();
int64_t FindDirectoryObjectID(const std::string &rDirName,
bool AllowOldVersion = false, bool AllowDeletedDirs = false,
std::vector<std::pair<std::string, int64_t> > *pStack = 0);
+
+private:
+
+ // Utility functions
int64_t FindFileID(const std::string& rNameOrIdString,
const bool *opts, int64_t *pDirIdOut,
std::string* pFileNameOut, int16_t flagsInclude,
int16_t flagsExclude, int16_t* pFlagsOut);
- int64_t GetCurrentDirectoryID();
std::string GetCurrentDirectoryName();
void SetReturnCode(int code) {mReturnCode = code;}
@@ -399,5 +381,33 @@ private:
int mReturnCode;
};
+typedef std::vector<std::string> (*CompletionHandler)
+ (BackupQueries::ParsedCommand& rCommand, const std::string& prefix,
+ BackupProtocolClient& rProtocol, const Configuration& rConfig,
+ BackupQueries& rQueries);
+
+std::vector<std::string> CompleteCommand(BackupQueries::ParsedCommand& rCommand,
+ const std::string& prefix, BackupProtocolClient& rProtocol,
+ const Configuration& rConfig, BackupQueries& rQueries);
+std::vector<std::string> CompleteOptions(BackupQueries::ParsedCommand& rCommand,
+ const std::string& prefix, BackupProtocolClient& rProtocol,
+ const Configuration& rConfig, BackupQueries& rQueries);
+
+#define MAX_COMPLETION_HANDLERS 4
+
+struct QueryCommandSpecification
+{
+ const char* name;
+ const char* opts;
+ CommandType type;
+ CompletionHandler complete[MAX_COMPLETION_HANDLERS];
+};
+
+// Data about commands
+extern QueryCommandSpecification commands[];
+
+extern const char *alias[];
+extern const int aliasIs[];
+
#endif // BACKUPQUERIES__H
diff --git a/bin/bbackupquery/bbackupquery.cpp b/bin/bbackupquery/bbackupquery.cpp
index 90fa74b5..bc4cff13 100644
--- a/bin/bbackupquery/bbackupquery.cpp
+++ b/bin/bbackupquery/bbackupquery.cpp
@@ -30,6 +30,7 @@
#include <readline.h>
#endif
#endif
+
#ifdef HAVE_READLINE_HISTORY
#ifdef HAVE_READLINE_HISTORY_H
#include <readline/history.h>
@@ -92,49 +93,70 @@ void PrintUsageAndExit()
}
#ifdef HAVE_LIBREADLINE
-// copied from: http://tiswww.case.edu/php/chet/readline/readline.html#SEC44
+static BackupProtocolClient* pProtocol;
+static const Configuration* pConfig;
+static BackupQueries* pQueries;
+static std::vector<std::string> completions;
+static std::auto_ptr<BackupQueries::ParsedCommand> sapCmd;
-char * command_generator(const char *text, int state)
+char * completion_generator(const char *text, int state)
{
- static int list_index, len;
- const char *name;
-
- /*
- * If this is a new word to complete, initialize now. This includes
- * saving the length of TEXT for efficiency, and initializing the index
- * variable to 0.
- */
- if(!state)
+ if(state == 0)
{
- list_index = 0;
- len = strlen(text);
- }
+ completions.clear();
- /* Return the next name which partially matches from the command list. */
- while((name = commands[list_index].name))
- {
- list_index++;
+ std::string partialCommand(rl_line_buffer, rl_point);
+ sapCmd.reset(new BackupQueries::ParsedCommand(partialCommand,
+ false));
- if(::strncmp(name, text, len) == 0 && !(state--))
+ if(sapCmd->mArgCount == 0) // incomplete command
{
- return ::strdup(name);
+ completions = CompleteCommand(*sapCmd, text, *pProtocol,
+ *pConfig, *pQueries);
}
- }
+ else if(sapCmd->mInOptions)
+ {
+ completions = CompleteOptions(*sapCmd, text, *pProtocol,
+ *pConfig, *pQueries);
+ }
+ else if(sapCmd->mArgCount - 1 < MAX_COMPLETION_HANDLERS)
+ // sapCmd->mArgCount must be at least 1 if we're here
+ {
+ CompletionHandler handler =
+ sapCmd->pSpec->complete[sapCmd->mArgCount - 1];
+ if(handler != NULL)
+ {
+ completions = handler(*sapCmd, text, *pProtocol,
+ *pConfig, *pQueries);
+ }
- list_index = 0;
+ if(std::string(text) == "")
+ {
+ // additional options are also allowed here
+ std::vector<std::string> addOpts =
+ CompleteOptions(*sapCmd, text,
+ *pProtocol, *pConfig,
+ *pQueries);
+
+ for(std::vector<std::string>::iterator
+ i = addOpts.begin();
+ i != addOpts.end(); i++)
+ {
+ completions.push_back(*i);
+ }
+ }
+ }
+ }
- while((name = alias[list_index]))
+ if(state < 0 || state >= (int) completions.size())
{
- list_index++;
-
- if(::strncmp(name, text, len) == 0 && !(state--))
- {
- return ::strdup(name);
- }
+ rl_attempted_completion_over = 1;
+ return NULL;
}
- /* If no names matched, then return NULL. */
- return (char *) NULL;
+ return strdup(completions[state].c_str());
+ // string must be allocated with malloc() and will be freed
+ // by rl_completion_matches().
}
#ifdef HAVE_RL_COMPLETION_MATCHES
@@ -145,22 +167,7 @@ char * command_generator(const char *text, int state)
char ** bbackupquery_completion(const char *text, int start, int end)
{
- char **matches;
-
- matches = (char **)NULL;
-
- /* If this word is at the start of the line, then it is a command
- * to complete. Otherwise it is the name of a file in the current
- * directory.
- */
- #ifdef RL_COMPLETION_MATCHES
- if (start == 0)
- {
- matches = RL_COMPLETION_MATCHES(text, command_generator);
- }
- #endif
-
- return matches;
+ return RL_COMPLETION_MATCHES(text, completion_generator);
}
#endif // HAVE_LIBREADLINE
@@ -445,10 +452,9 @@ int main(int argc, const char *argv[])
int c = 0;
while(c < argc && !context.Stop())
{
- BackupQueries::ParsedCommand cmd(
- context.ParseCommand(argv[c++], true));
+ BackupQueries::ParsedCommand cmd(argv[c++], true);
- if(cmd.failed)
+ if(cmd.mFailed)
{
BOX_ERROR("Parse failed");
}
@@ -487,6 +493,10 @@ int main(int argc, const char *argv[])
/* Tell the completer that we want a crack first. */
rl_attempted_completion_function = bbackupquery_completion;
+ pProtocol = &connection;
+ pConfig = &conf;
+ pQueries = &context;
+
char *last_cmd = 0;
while(!context.Stop())
{
@@ -498,10 +508,9 @@ int main(int argc, const char *argv[])
break;
}
- BackupQueries::ParsedCommand cmd(
- context.ParseCommand(command, false));
+ BackupQueries::ParsedCommand cmd(command, false);
- if(cmd.failed)
+ if(cmd.mFailed)
{
BOX_ERROR("Parse failed");
}
@@ -541,8 +550,8 @@ int main(int argc, const char *argv[])
printf("query > ");
fflush(stdout);
std::string command(getLine.GetLine());
- BackupQueries::ParsedCommand cmd(
- context.ParseCommand(command, false));
+ BackupQueries::ParsedCommand cmd(command,
+ false);
context.DoCommand(cmd);
}
}