diff options
Diffstat (limited to 'bin/bbackupquery')
-rw-r--r-- | bin/bbackupquery/BackupQueries.cpp | 554 | ||||
-rw-r--r-- | bin/bbackupquery/BackupQueries.h | 84 | ||||
-rw-r--r-- | bin/bbackupquery/bbackupquery.cpp | 119 |
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); } } |