source: box/trunk/bin/bbackupquery/CommandCompletion.cpp @ 2983

Revision 2983, 12.9 KB checked in by chris, 9 months ago (diff)

Combine client and server protocols to make way for an offline/local protocol.

Rename ProtocolObject? to Message.

  • Property svn:eol-style set to native
Line 
1// --------------------------------------------------------------------------
2//
3// File
4//              Name:    CommandCompletion.cpp
5//              Purpose: Parts of BackupQueries that depend on readline
6//              Created: 2011/01/21
7//
8// --------------------------------------------------------------------------
9
10#include "Box.h"
11
12#ifdef HAVE_LIBREADLINE
13        #ifdef HAVE_READLINE_READLINE_H
14                #include <readline/readline.h>
15        #elif defined(HAVE_EDITLINE_READLINE_H)
16                #include <editline/readline.h>
17        #elif defined(HAVE_READLINE_H)
18                #include <readline.h>
19        #endif
20#endif
21
22#ifdef HAVE_READLINE_HISTORY
23        #ifdef HAVE_READLINE_HISTORY_H
24                #include <readline/history.h>
25        #elif defined(HAVE_HISTORY_H)
26                #include <history.h>
27        #endif
28#endif
29
30#include <cstring>
31
32#include "BackupQueries.h"
33#include "Configuration.h"
34
35#include "autogen_BackupProtocol.h"
36
37#include "MemLeakFindOn.h"
38
39#define COMPARE_RETURN_SAME             1
40#define COMPARE_RETURN_DIFFERENT        2
41#define COMPARE_RETURN_ERROR            3
42#define COMMAND_RETURN_ERROR            4
43
44#define COMPLETION_FUNCTION(name, code) \
45std::vector<std::string> Complete ## name( \
46        BackupQueries::ParsedCommand& rCommand, \
47        const std::string& prefix, \
48        BackupProtocolClient& rProtocol, const Configuration& rConfig, \
49        BackupQueries& rQueries) \
50{ \
51        std::vector<std::string> completions; \
52        \
53        try \
54        { \
55                code \
56        } \
57        catch(std::exception &e) \
58        { \
59                BOX_TRACE("Failed to complete " << prefix << ": " << e.what()); \
60        } \
61        catch(...) \
62        { \
63                BOX_TRACE("Failed to complete " << prefix << ": " \
64                        "unknown error"); \
65        } \
66        \
67        return completions; \
68}
69
70#define DELEGATE_COMPLETION(name) \
71        completions = Complete ## name(rCommand, prefix, rProtocol, rConfig, \
72        rQueries);
73
74COMPLETION_FUNCTION(None,)
75
76#ifdef HAVE_RL_FILENAME_COMPLETION_FUNCTION
77        #define RL_FILENAME_COMPLETION_FUNCTION rl_filename_completion_function
78        #define HAVE_A_FILENAME_COMPLETION_FUNCTION 1
79#elif defined HAVE_FILENAME_COMPLETION_FUNCTION
80        #define RL_FILENAME_COMPLETION_FUNCTION filename_completion_function
81        #define HAVE_A_FILENAME_COMPLETION_FUNCTION 1
82#endif
83
84#ifdef HAVE_A_FILENAME_COMPLETION_FUNCTION
85COMPLETION_FUNCTION(Default,
86        while (const char *match = RL_FILENAME_COMPLETION_FUNCTION(prefix.c_str(), 0))
87        {
88                completions.push_back(match);
89        }
90)
91#else // !HAVE_A_FILENAME_COMPLETION_FUNCTION
92COMPLETION_FUNCTION(Default,)
93#endif // HAVE_A_FILENAME_COMPLETION_FUNCTION
94
95COMPLETION_FUNCTION(Command,
96        int len = prefix.length();
97
98        for(int i = 0; commands[i].name != NULL; i++)
99        {
100                if(::strncmp(commands[i].name, prefix.c_str(), len) == 0)
101                {
102                        completions.push_back(commands[i].name);
103                }
104        }
105)
106
107void CompleteOptionsInternal(const std::string& prefix,
108        BackupQueries::ParsedCommand& rCommand,
109        std::vector<std::string>& completions)
110{
111        std::string availableOptions = rCommand.pSpec->opts;
112
113        for(std::string::iterator
114                opt =  availableOptions.begin();
115                opt != availableOptions.end(); opt++)
116        {
117                if(rCommand.mOptions.find(*opt) == std::string::npos)
118                {
119                        if(prefix == "")
120                        {
121                                // complete with possible option strings
122                                completions.push_back(std::string("-") + *opt);
123                        }
124                        else
125                        {
126                                // complete with possible additional options
127                                completions.push_back(prefix + *opt);
128                        }
129                }
130        }
131}
132
133COMPLETION_FUNCTION(Options,
134        CompleteOptionsInternal(prefix, rCommand, completions);
135)
136
137std::string EncodeFileName(const std::string &rUnEncodedName)
138{
139#ifdef WIN32
140        std::string encodedName;
141        if(!ConvertConsoleToUtf8(rUnEncodedName, encodedName))
142        {
143                return std::string();
144        }
145        return encodedName;
146#else
147        return rUnEncodedName;
148#endif
149}
150
151int16_t GetExcludeFlags(BackupQueries::ParsedCommand& rCommand)
152{
153        int16_t excludeFlags = 0;
154
155        if (rCommand.mOptions.find(LIST_OPTION_ALLOWOLD) == std::string::npos)
156        {
157                excludeFlags |= BackupProtocolListDirectory::Flags_OldVersion;
158        }
159
160        if (rCommand.mOptions.find(LIST_OPTION_ALLOWDELETED) == std::string::npos)
161        {
162                excludeFlags |= BackupProtocolListDirectory::Flags_Deleted;
163        }
164
165        return excludeFlags;
166}
167
168std::vector<std::string> CompleteRemoteFileOrDirectory(
169        BackupQueries::ParsedCommand& rCommand,
170        const std::string& prefix, BackupProtocolClient& rProtocol,
171        BackupQueries& rQueries, int16_t includeFlags)
172{
173        std::vector<std::string> completions;
174       
175        // default to using the current directory
176        int64_t listDirId = rQueries.GetCurrentDirectoryID();
177        std::string searchPrefix;
178        std::string listDir = prefix;
179
180        if(rCommand.mArgCount == rCommand.mCmdElements.size())
181        {
182                // completing an empty name, from the current directory
183                // nothing to change
184        }
185        else
186        {
187                // completing a partially-completed subdirectory name
188                searchPrefix = prefix;
189                listDir = "";
190
191                // do we need to list a subdirectory to complete?
192                size_t lastSlash = searchPrefix.rfind('/');
193                if(lastSlash == std::string::npos)
194                {
195                        // no slashes, so the whole name is the prefix
196                        // nothing to change
197                }
198                else
199                {
200                        // listing a partially-completed subdirectory name
201                        listDir = searchPrefix.substr(0, lastSlash);
202
203                        listDirId = rQueries.FindDirectoryObjectID(listDir,
204                                rCommand.mOptions.find(LIST_OPTION_ALLOWOLD)
205                                        != std::string::npos,
206                                rCommand.mOptions.find(LIST_OPTION_ALLOWDELETED)
207                                        != std::string::npos);
208
209                        if(listDirId == 0)
210                        {
211                                // no matches for subdir to list,
212                                // return empty-handed.
213                                return completions;
214                        }
215
216                        // matched, and updated listDir and listDirId already
217                        searchPrefix = searchPrefix.substr(lastSlash + 1);
218                }
219        }
220
221        // Always include directories, because they contain files.
222        // We will append a slash later for each directory if we're
223        // actually looking for files.
224        //
225        // If we're looking for directories, then only list directories.
226
227        bool completeFiles = includeFlags &
228                BackupProtocolListDirectory::Flags_File;
229        bool completeDirs = includeFlags &
230                BackupProtocolListDirectory::Flags_Dir;
231        int16_t listFlags = 0;
232
233        if(completeFiles)
234        {
235                listFlags = BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING;
236        }
237        else if(completeDirs)
238        {
239                listFlags = BackupProtocolListDirectory::Flags_Dir;
240        }
241
242        rProtocol.QueryListDirectory(listDirId,
243                listFlags, GetExcludeFlags(rCommand),
244                false /* no attributes */);
245
246        // Retrieve the directory from the stream following
247        BackupStoreDirectory dir;
248        std::auto_ptr<IOStream> dirstream(rProtocol.ReceiveStream());
249        dir.ReadFromStream(*dirstream, rProtocol.GetTimeout());
250
251        // Then... display everything
252        BackupStoreDirectory::Iterator i(dir);
253        BackupStoreDirectory::Entry *en = 0;
254        while((en = i.Next()) != 0)
255        {
256                BackupStoreFilenameClear clear(en->GetName());
257                std::string name = clear.GetClearFilename().c_str();
258                if(name.compare(0, searchPrefix.length(), searchPrefix) == 0)
259                {
260                        if(en->IsDir() &&
261                                (includeFlags & BackupProtocolListDirectory::Flags_Dir) == 0)
262                        {
263                                // Was looking for a file, but this is a
264                                // directory, so append a slash to the name
265                                name += "/";
266                        }
267
268                        if(listDir == "")
269                        {
270                                completions.push_back(name);
271                        }
272                        else
273                        {
274                                completions.push_back(listDir + "/" + name);
275                        }
276                }
277        }
278
279        return completions;
280}
281
282COMPLETION_FUNCTION(RemoteDir,
283        completions = CompleteRemoteFileOrDirectory(rCommand, prefix,
284                rProtocol, rQueries,
285                BackupProtocolListDirectory::Flags_Dir);
286)
287
288COMPLETION_FUNCTION(RemoteFile,
289        completions = CompleteRemoteFileOrDirectory(rCommand, prefix,
290                rProtocol, rQueries,
291                BackupProtocolListDirectory::Flags_File);
292)
293
294COMPLETION_FUNCTION(LocalDir,
295        DELEGATE_COMPLETION(Default);
296)
297
298COMPLETION_FUNCTION(LocalFile,
299        DELEGATE_COMPLETION(Default);
300)
301
302COMPLETION_FUNCTION(LocationName,
303        const Configuration &locations(rConfig.GetSubConfiguration(
304                "BackupLocations"));
305
306        std::vector<std::string> locNames =
307                locations.GetSubConfigurationNames();
308
309        for(std::vector<std::string>::iterator
310                pLocName  = locNames.begin();
311                pLocName != locNames.end();
312                pLocName++)
313        {
314                if(pLocName->compare(0, pLocName->length(), prefix) == 0)
315                {
316                        completions.push_back(*pLocName);
317                }
318        }
319)
320
321COMPLETION_FUNCTION(RemoteFileIdInCurrentDir,
322        int64_t listDirId = rQueries.GetCurrentDirectoryID();
323        int16_t excludeFlags = GetExcludeFlags(rCommand);
324
325        rProtocol.QueryListDirectory(
326                listDirId,
327                BackupProtocolListDirectory::Flags_File,
328                excludeFlags, false /* no attributes */);
329
330        // Retrieve the directory from the stream following
331        BackupStoreDirectory dir;
332        std::auto_ptr<IOStream> dirstream(rProtocol.ReceiveStream());
333        dir.ReadFromStream(*dirstream, rProtocol.GetTimeout());
334
335        // Then... compare each item
336        BackupStoreDirectory::Iterator i(dir);
337        BackupStoreDirectory::Entry *en = 0;
338        while((en = i.Next()) != 0)
339        {
340                std::ostringstream hexId;
341                hexId << std::hex << en->GetObjectID();
342                if(hexId.str().compare(0, prefix.length(), prefix) == 0)
343                {
344                        completions.push_back(hexId.str());
345                }
346        }
347)
348
349// TODO implement completion of hex IDs up to the maximum according to Usage
350COMPLETION_FUNCTION(RemoteId,)
351
352COMPLETION_FUNCTION(GetFileOrId,
353        if(rCommand.mOptions.find('i') != std::string::npos)
354        {
355                DELEGATE_COMPLETION(RemoteFileIdInCurrentDir);
356        }
357        else
358        {
359                DELEGATE_COMPLETION(RemoteFile);
360        }
361)
362
363COMPLETION_FUNCTION(CompareLocationOrRemoteDir,
364        if(rCommand.mOptions.find('l') != std::string::npos)
365        {
366                DELEGATE_COMPLETION(LocationName);
367        }
368        else
369        {
370                DELEGATE_COMPLETION(RemoteDir);
371        }
372)
373
374COMPLETION_FUNCTION(CompareNoneOrLocalDir,
375        if(rCommand.mOptions.find('l') != std::string::npos)
376        {
377                // no completions
378                DELEGATE_COMPLETION(None);
379        }
380        else
381        {
382                DELEGATE_COMPLETION(LocalDir);
383        }
384)
385
386COMPLETION_FUNCTION(RestoreRemoteDirOrId,
387        if(rCommand.mOptions.find('i') != std::string::npos)
388        {
389                DELEGATE_COMPLETION(RemoteId);
390        }
391        else
392        {
393                DELEGATE_COMPLETION(RemoteDir);
394        }
395)
396
397// Data about commands
398QueryCommandSpecification commands[] = 
399{
400        { "quit",       "",             Command_Quit,   {} },
401        { "exit",       "",             Command_Quit,   {} },
402        { "list",       "rodIFtTash",   Command_List,   {CompleteRemoteDir} },
403        { "pwd",        "",             Command_pwd,    {} },
404        { "cd",         "od",           Command_cd,     {CompleteRemoteDir} },
405        { "lcd",        "",             Command_lcd,    {CompleteLocalDir} },
406        { "sh",         "",             Command_sh,     {CompleteDefault} },
407        { "getobject",  "",             Command_GetObject,
408                {CompleteRemoteId, CompleteLocalDir} },
409        { "get",        "i",            Command_Get,
410                {CompleteGetFileOrId, CompleteLocalDir} },
411        { "compare",    "alcqAEQ",      Command_Compare,
412                {CompleteCompareLocationOrRemoteDir, CompleteCompareNoneOrLocalDir} },
413        { "restore",    "drif",         Command_Restore,
414                {CompleteRestoreRemoteDirOrId, CompleteLocalDir} },
415        { "help",       "",             Command_Help,   {} },
416        { "usage",      "m",            Command_Usage,  {} },
417        { "undelete",   "i",            Command_Undelete,
418                {CompleteGetFileOrId} },
419        { "delete",     "i",            Command_Delete, {CompleteGetFileOrId} },
420        { NULL,         NULL,           Command_Unknown, {} } 
421};
422
423const char *alias[] = {"ls", 0};
424const int aliasIs[] = {Command_List, 0};
425
426BackupQueries::ParsedCommand::ParsedCommand(const std::string& Command,
427        bool isFromCommandLine)
428: mInOptions(false),
429  mFailed(false),
430  pSpec(NULL),
431  mArgCount(0)
432{
433        mCompleteCommand = Command;
434       
435        // is the command a shell command?
436        if(Command[0] == 's' && Command[1] == 'h' && Command[2] == ' ' && Command[3] != '\0')
437        {
438                // Yes, run shell command
439                for(int i = 0; commands[i].type != Command_Unknown; i++)
440                {
441                        if(commands[i].type == Command_sh)
442                        {
443                                pSpec = &(commands[i]);
444                                break;
445                        }
446                }
447
448                mCmdElements[0] = "sh";
449                mCmdElements[1] = Command.c_str() + 3;
450                return;
451        }
452
453        // split command into components
454        const char *c = Command.c_str();
455        bool inQuoted = false;
456        mInOptions = false;
457       
458        std::string s;
459        while(*c != 0)
460        {
461                // Terminating char?
462                if(*c == ((inQuoted)?'"':' '))
463                {
464                        if(!s.empty())
465                        {
466                                mCmdElements.push_back(s);
467
468                                // Because we just parsed a space, if this
469                                // wasn't an option word, then we're now
470                                // completing the next (or first) arg
471                                if(!mInOptions)
472                                {
473                                        mArgCount++;
474                                }
475                        }
476
477                        s.resize(0);
478                        inQuoted = false;
479                        mInOptions = false;
480                }
481                else
482                {
483                        // No. Start of quoted parameter?
484                        if(s.empty() && *c == '"')
485                        {
486                                inQuoted = true;
487                        }
488                        // Start of options?
489                        else if(s.empty() && *c == '-')
490                        {
491                                mInOptions = true;
492                        }
493                        else
494                        {
495                                if(mInOptions)
496                                {
497                                        // Option char
498                                        mOptions += *c;
499                                }
500                                else
501                                {
502                                        // Normal string char
503                                        s += *c;
504                                }
505                        }
506                }
507       
508                ++c;
509        }
510       
511        if(!s.empty())
512        {
513                mCmdElements.push_back(s);
514        }
515
516        // Work out which command it is...
517        int cmd = 0;
518        while(mCmdElements.size() > 0 && commands[cmd].name != 0 && 
519                mCmdElements[0] != commands[cmd].name)
520        {
521                cmd++;
522        }
523       
524        if(mCmdElements.size() > 0 && commands[cmd].name == 0)
525        {
526                // Check for aliases
527                int a;
528                for(a = 0; alias[a] != 0; ++a)
529                {
530                        if(mCmdElements[0] == alias[a])
531                        {
532                                // Found an alias
533                                cmd = aliasIs[a];
534                                break;
535                        }
536                }
537        }
538
539        if(mCmdElements.size() == 0 || commands[cmd].name == 0)
540        {
541                mFailed = true;
542                return;
543        }
544
545        pSpec = &(commands[cmd]);
546       
547        #ifdef WIN32
548        if(isFromCommandLine)
549        {
550                std::string converted;
551               
552                if(!ConvertEncoding(mCompleteCommand, CP_ACP, converted, 
553                        GetConsoleCP()))
554                {
555                        BOX_ERROR("Failed to convert encoding");
556                        mFailed = true;
557                }
558               
559                mCompleteCommand = converted;
560               
561                for(std::vector<std::string>::iterator
562                        i  = mCmdElements.begin();
563                        i != mCmdElements.end(); i++)
564                {
565                        if(!ConvertEncoding(*i, CP_ACP, converted, 
566                                GetConsoleCP()))
567                        {
568                                BOX_ERROR("Failed to convert encoding");
569                                mFailed = true;
570                        }
571                       
572                        *i = converted;
573                }
574        }
575        #endif
576}
577
Note: See TracBrowser for help on using the repository browser.