/*============================================================================= # Filename: gconsole.cpp # Author: Bookug Lobert, modified by Wang Libo # Mail: 1181955272@qq.com # Last Modified: 2016-07-20 19:28 # Description: This is a console integrating all commands in Gstore System and others. It provides completion of command names, line editing features, and access to the history list. NOTICE: no separators required in the end of your commands, and please just type one command at a time. If there are many instructions to execute, please write them in a file like test.sql, and tell the gconsole to use this file =============================================================================*/ #include "../Database/Database.h" #include "../Util/Util.h" #include "GstoreConnector.h" using namespace std; //NOTICE: not imitate the usage of gload/gquery/gclient/gserver in command line //but need to support the query scripts(so support parameters indirectly) //The names of functions that actually do the manipulation. //common commands int help_handler (const vector&); int source_handler (const vector&); int quit_handler (const vector&); //C/S commands int connect_handler (const vector&); int disconnect_handler (const vector&); //system commands int list_handler (const vector&); int view_handler (const vector&); int rename_handler (const vector&); int stat_handler (const vector&); int pwd_handler (const vector&); int delete_handler (const vector&); int cd_handler (const vector&); // server commands int start_handler (const vector&); int stop_handler (const vector&); //remote commands int build_handler (const vector&); int drop_handler (const vector&); int load_handler (const vector&); int unload_handler (const vector&); int query_handler (const vector&); int show_handler (const vector&); //A structure which contains information on the commands this program can understand. typedef struct { const char *name; // User printable name of the function int (*func)(const vector&); // Function to call to do the job const char *doc; // Documentation for this function } COMMAND; // COMMAND native_commands[] = { { "help", help_handler, "Display this text." }, { "?", help_handler, "Synonym for \"help\"." }, { "source", source_handler, "Use a file containing SPARQL queries." }, { "quit", quit_handler, "Quit this console." }, { "connect", connect_handler, "Connect to a server running Gstore." }, { "show", show_handler, "Show the database name which is used now." }, { "build", build_handler, "Build a database from a dataset." }, { "drop", drop_handler, "Drop a database according to the given path." }, { "load", load_handler, "Load a existing database." }, { "unload", unload_handler, "Unload the current used database." }, { "query", query_handler, "Answer a SPARQL query." }, { "start", start_handler, "Start local server." }, { NULL, NULL, NULL } }; // COMMAND remote_commands[] = { { "help", help_handler, "Display this text." }, { "?", help_handler, "Synonym for \"help\"." }, { "source", source_handler, "Use a file containing SPARQL queries." }, { "show", show_handler, "Show the database name which is used now." }, { "build", build_handler, "Build a database from a dataset." }, { "drop", drop_handler, "Drop a database according to the given path." }, { "load", load_handler, "Load a existing database." }, { "unload", unload_handler, "Unload the current used database." }, { "query", query_handler, "Answer a SPARQL query." }, { "disconnect", disconnect_handler, "Disconnect the current server connection." }, { "stop", stop_handler, "Stop server and disconnect." }, { NULL, NULL, NULL } }; COMMAND *current_commands = native_commands; //according to gc ?= NULL char *dupstr(const char*); char *stripwhite(char *); COMMAND *find_command(char *); void initialize_readline(); int execute_line(char *); int deal_with_script(char *); bool parse_arguments(char *, vector&); int save_history(); int load_history(); // Global variables //The name of this program, as taken from argv[0]. char *progname; // //When true, this global means the user is done using this program. bool done = false; //still running //server-client mode or local engine mode GstoreConnector *gc = NULL; //local mode by default //redirect mechanism, only useful for query FILE* output = stdout; //current using database in local Database *current_database = NULL; //TODO:redirect 2>&1 or adjust the fprintf->stderr to file pointer int main(int argc, char **argv) { //NOTICE:this is needed to ensure the file path is the work path //chdir(dirname(argv[0])); //NOTICE:this is needed to set several debug files #ifdef DEBUG Util util; #endif char *line, *s; progname = argv[0]; system("clear"); //the info to be printed cout << endl; cout << "Gstore Console(gconsole), an interactive shell based utility to communicate with gStore repositories." << endl; cout << "usage: start-gconsole [OPTION]" << endl; cout << " -h, --help print this help" << endl; cout << " -s, --source source the SPARQL script" << endl; cout << "For bug reports and suggestions, see https://github.com/Caesar11/gStore" << endl << endl << endl; if (argc > 1) { if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) { cout << "Type \"?\" or \"help\" in the console to see info of all commands" << endl; if (argc > 2) { cerr << "Nonsense to add more parameters!" << endl; } return 0; } else if (strcmp(argv[1], "-s") == 0 || strcmp(argv[1], "--source") == 0) { if (argc != 3) { cerr << "You should just add one script file to be sourced!" << endl; return 1; } return deal_with_script(argv[2]); } else { cerr << "Wrong option used, please see the help info first!" << endl; return 1; } } cout << "Notice that commands are a little different between native mode and remote mode." << endl; cout << "Now is in native mode, please type your commands." << endl; cout << "Please do not use any separators in the end." << endl << endl; initialize_readline(); //Bind our completer using_history(); load_history(); //Loop reading and executing lines until the user quits. while (!done) { if (gc == NULL) { line = readline("gstore>"); } else { line = readline("server>"); } //BETTER:multi lines input in alignment?need separators like ';' in gclient.cpp //For simplicity, we do not use this feature here. if (line == NULL) //EOF or Ctrl-D { if (current_database != NULL) { cerr << endl << "Please unload your database before quiting!" << endl << endl; continue; } cout << endl << endl; break; } //Remove leading and trailing whitespace from the line. //Then, if there is anything left, add it to the history //list and execute it. s = stripwhite(line); if (*s) { add_history(s); execute_line(s); } free(line); } save_history(); exit(0); } /* **************************************************************** */ /* */ /* Some Utilities */ /* */ /* **************************************************************** */ char * dupstr(const char *s) { char *r; int len = strlen(s) + 1; r = (char *)malloc(len); //BETTER:xmalloc? memset(r, 0, sizeof(char) * (len)); strcpy(r, s); return r; } // Execute a command line int execute_line(char *line) { int i; COMMAND *cmd; char *word = NULL; //to find if redirected bool is_redirected = false; int j = strlen(line) - 1; while (j > -1) { if (line[j] == '"') break; else if (line[j] == '>') { is_redirected = true; i = j; break; } else j--; } if (is_redirected) { j++; while (line[j] && whitespace(line[j])) { j++; } cout << "The file is: " << (line + j) << endl; output = fopen(line + j, "w+"); if (output == NULL) { cout << "Failed to open " << (line + j) << endl; output = stdout; } line[i] = '\0'; } //BETTER: how about >> ? //Isolate the command word. i = 0; while (line[i] && whitespace(line[i])) i++; word = line + i; while (line[i] && !whitespace(line[i])) i++; if (line[i]) line[i++] = '\0'; cmd = find_command(word); if (!cmd) { if (gc == NULL) { cout << "Now is in native mode!" << endl; } else { cout << "Now is in remote mode!" << endl; } cout << word << ": No such command for gconsole" << endl << endl; if (output != stdout) { fclose(output); output = stdout; } return -1; } //Get argument to command, if any. while (line[i] && whitespace(line[i])) i++; word = line + i; //cout << word << endl; //debug vector args; int ret; if (parse_arguments(word, args)) { ret = cmd->func(args); } else { ret = -1; } #ifdef DEBUG_PRECISE msg << "All done, now to close the file." << endl; #endif if (output != stdout) { fclose(output); output = stdout; } cout << endl; return ret; } // Look up NAME as the name of a command, and return a pointer to that // command. Return a NULL pointer if NAME isn't a command name. COMMAND * find_command(char *name) { int i; for (i = 0; current_commands[i].name; i++) { if (strcmp(name, current_commands[i].name) == 0) return ¤t_commands[i]; } return (COMMAND*)NULL; } // Strip whitespace from the start and end of STRING. Return a pointer into STRING. char * stripwhite(char *string) { char *s, *t; for (s = string; whitespace(*s); s++); if (*s == 0) return s; t = s + strlen(s) - 1; while (t > s && (whitespace(*t) || *t == '\r' || *t == '\n')) { t--; } *++t = '\0'; return s; } //support commands scripts //QUERY:source another file again(how about exactly this script twice or more) int deal_with_script(char* file) { FILE* fp = NULL; if ((fp = fopen(file, "r")) == NULL) { cerr << "Open error: " << file << endl; return -1; } //WARN:the length of each line in the script should not exceed 500 char line[505], *s = NULL; while ((fgets(line, 501, fp)) != NULL) { //NOTICE:empty line here also contains '\n' if (strlen(line) == 1) continue; s = stripwhite(line); if (*s) { execute_line(s); } } //end of file if (current_database != NULL) { cerr << endl << "Please unload your database before quitting!" << endl << endl; //TODO } if (gc != NULL) { cerr << endl << "Please return to native mode before quitting!" << endl << endl; //TODO } return 0; } // Parse arguments bool parse_arguments(char* word, vector& args) { if (word == NULL) { return true; } while (*word) { int i = 0; if (*word == '\"') { i++; while (word[i] && word[i] != '\"') { i++; } char tmp = word[i + 1]; if (word[i] == '\"' && (whitespace(tmp) || tmp == '\0')) { word[++i] = '\0'; args.push_back(string(word)); //cout << args.size() - 1 << '\t' << args.back() << endl; //debug word[i] = tmp; while (word[i] && whitespace(word[i])) { i++; } word += i; continue; } else { cerr << "Invalid arguments!" << endl; return false; } } while (word[i] && !whitespace(word[i])) { i++; } char tmp = word[i]; word[i] = '\0'; args.push_back(string(word)); //cout << args.size() - 1 << '\t' << args.back() << endl; //debug word[i] = tmp; while (word[i] && whitespace(word[i])) { i++; } word += i; } return true; } // Save command history // Notice that only by quitting gconsole normally can command history be saved int save_history() { //Limit the number of history to save static const int max_history = 1024; stifle_history(max_history); HIST_ENTRY ** pHisList = history_list(); if (pHisList == NULL) { return 0; } ofstream fout("bin/.gconsole_history", ios::out); if (!fout) { return -1; } while (pHisList[0] != NULL) { fout << string(pHisList[0]->line) << endl; pHisList++; } fout.close(); return 0; } // Load command history int load_history() { ifstream fin("bin/.gconsole_history", ios::in); if (!fin) { return -1; } const int line_length = 1024; char line[line_length]; while (fin.getline(line, line_length)) { add_history(line); } fin.close(); return 0; } /* **************************************************************** */ /* */ /* Interface to Readline Completion */ /* */ /* **************************************************************** */ char *command_generator (const char *, int); char **gconsole_completion (const char *, int, int); /* Tell the GNU Readline library how to complete. We want to try to complete on command names if this is the first word in the line, or on filenames if not. */ void initialize_readline() { /* Allow conditional parsing of the ~/.inputrc file. */ rl_readline_name = "gconsole"; /* Tell the completer that we want a crack first. */ rl_attempted_completion_function = gconsole_completion; } /* Attempt to complete on the contents of TEXT. START and END bound the region of rl_line_buffer that contains the word to complete. TEXT is the word to complete. We can use the entire contents of rl_line_buffer in case we want to do some simple parsing. Return the array of matches, or NULL if there aren't any. */ char ** gconsole_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. if (start == 0) { matches = rl_completion_matches(text, command_generator); } return matches; } /* Generator function for command completion. STATE lets us know whether to start from scratch; without any state(i.e. STATE == 0), then we start at the top of the list. */ char * command_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) { list_index = 0; len = strlen(text); } /* Return the next name which partially matches from the command list. */ while ((name = current_commands[list_index].name) != NULL) { list_index++; if (strncmp(name, text, len) == 0) return dupstr(name); } /* If no names matched, then return NULL. */ return (char *)NULL; } /* **************************************************************** */ /* */ /* gconsole commands */ /* */ /* **************************************************************** */ //Print out help for ARG, or for all of the commands if ARG is not present. int help_handler(const vector& args) { switch (args.size()) { case 0: { int i; for (i = 0; current_commands[i].name; i++) { cout << current_commands[i].name << "\t\t" << current_commands[i].doc << endl; } break; } case 1: { int i; int printed = 0; for (i = 0; current_commands[i].name; i++) { if (args[0] == current_commands[i].name) { cout << current_commands[i].name << "\t\t" << current_commands[i].doc << endl; printed++; break; } } if (printed == 0) { cerr << "No commands match \"" << args[0] << "\". Possibilities are:" << endl; for (i = 0; current_commands[i].name; i++) { //Print in six columns. if (printed == 6) { printed = 0; cout << endl; } cout << current_commands[i].name << '\t'; printed++; } if (printed) { cout << endl; } } break; } default: { cerr << "Too many arguments!" << endl; return -1; } } return 0; } //NOTICE:the SPARQL file to be used should be placed in the local machine even when in remote mode int source_handler(const vector& args) { if (args.size() != 1) { cerr << "Exactly 1 argument required!" << endl; return -1; } char* str = dupstr(args[0].c_str()); int ret = deal_with_script(str); free(str); return ret; } int quit_handler(const vector& args) { if (!args.empty()) { cerr << "Too many arguments!" << endl; return -1; } if (gc != NULL) { cerr << "This command cannot be used when in remote mode." << endl; return -1; } if (current_database != NULL) { cerr << "Please unload your database before quitting." << endl; return -1; } done = true; return 0; } int connect_handler(const vector& args) { if (args.size() > 2) { cerr << "Too many arguments!" << endl; return -1; } if (gc != NULL) { cerr << "This command cannot be used when in remote mode." << endl; return -1; } if (current_database != NULL) { cerr << "Please unload your database before entering remote mode." << endl; return -1; } unsigned short port = GstoreConnector::defaultServerPort; string ip = GstoreConnector::defaultServerIP; if (args.size() == 2) { if (!Util::isValidIP(args[0])) { cerr << "Invalid IP: " << args[0] << endl; return -1; } if (!Util::isValidPort(args[1])) { cerr << "Invalid Port: " << args[1] << endl; return -1; } ip = args[0]; stringstream(args[1]) >> port; } else if (args.size() == 1) { if (Util::isValidIP(args[0])) { ip = args[0]; } else if (Util::isValidPort(args[0])) { stringstream(args[0]) >> port; } else { cerr << "Invalid argument, neither IP nor port: " << args[0] << endl; return -1; } } //initialize the GStore server's IP address and port. gc = new GstoreConnector(ip, port); if (!gc->test()) { cerr << "Failed to connect to server at " << ip << ':' << port << endl; delete gc; gc = NULL; return -1; } current_commands = remote_commands; cout << "Now is in remote mode, please type your commands." << endl; return 0; } int disconnect_handler(const vector& args) { if (!args.empty()) { cerr << "Too many arguments!" << endl; return -1; } if (gc == NULL) { cerr << "This command cannot be used when in native mode." << endl; return -1; } string show_ret = gc->show(); if (show_ret != "connect to server error" && show_ret != "send show command error." && show_ret != "\n[empty]\n") { cerr << "Please unload your server database before entering native mode." << endl; return -1; } delete gc; gc = NULL; current_commands = native_commands; cout << "Now is in native mode, please type your commands." << endl; return 0; } int show_handler(const vector& args) { if (args.size() > 1) { cerr << "Too many arguments!" << endl; return -1; } bool flag = false; if (args.size() == 1) { if (args[0] == "all") { flag = true; } else { cerr << "Invalid argument: " << args[0] << endl; return -1; } } if (gc != NULL) { string database = gc->show(flag); cout << database << endl; return 0; } //native mode if (flag) { string database = Util::getItemsFromDir(Util::db_home); if (database.empty()) { database = "No databases."; } cout << database << endl; return 0; } if (current_database == NULL) { cout << "No database used now." << endl; } else { cout << current_database->getName() << endl; } return 0; } //NOTICE: for build() and load(), always keep database in the root of gStore int build_handler(const vector& args) { if (args.size() != 2) { cerr << "Exactly 2 arguments required!" << endl; return -1; } string database = args[0]; //WARN:user better not end with ".db" by themselves!!! if (database.length() > 3 && database.substr(database.length() - 3, 3) == ".db") { cerr << "Your db name to be built should not end with \".db\"." << endl; return -1; } database += ".db"; //NOTICE: when in remote mode, the dataset should be placed in the server! And the exact path can only be got in the server //we can deal with it in Database string dataset = args[1]; //remote mode if (gc != NULL) { // QUERY:how to interact:string?dict(0:string)?json(service_id, service_args)? // Here uses the Cpp API Wrapper // build a new database by a RDF file. // note that the relative path is related to gserver. if (gc->build(database, dataset)) { return 0; } else { return -1; } } if (current_database != NULL) { cerr << "Please unload your database first." << endl; return -1; } cout << "Import dataset to build database..." << endl; cout << "DB_store: " << database << "\tRDF_data: " << dataset << endl; current_database = new Database(database); bool flag = current_database->build(dataset); delete current_database; current_database = NULL; if (!flag) { cerr << "Import RDF file to database failed." << endl; string cmd = "rm -rf " + database; system(cmd.c_str()); return -1; } cout << "Import RDF file to database done." << endl; return 0; } int drop_handler(const vector& args) { if (args.size() != 1) { cerr << "Exactly 1 argument required!" << endl; return -1; } //only drop when *.db, avoid other files be removed string database = args[0]; if (database.length() > 3 && database.substr(database.length() - 3, 3) == ".db") { cerr << "You should use exactly the same db name as building, which should not end with \".db\"" << endl; return -1; } database += ".db"; //remote mode if (gc != NULL) { if (gc->drop(database)) { return 0; } else { return -1; } } if (current_database != NULL) { cerr << "Please do not use this command when you are using a database." << endl; return -1; } string cmd = string("rm -rf ") + database; int ret = system(cmd.c_str()); cout << database << " dropped." << endl; return ret; } //NOTICE+WARN: //generally, datasets are very large while a query file cannot be too large. //So, when in remote mode, we expect that datasets in the server are used, while //queries in local machine are used(transformed to string and passed to server). int load_handler(const vector& args) { if (args.size() != 1) { cerr << "Exactly 1 argument is required!" << endl; return -1; } string database = args[0]; if (database.length() > 3 && database.substr(database.length() - 3, 3) == ".db") { cerr << "You should use exactly the same db name as building, which should not end with \".db\"" << endl; return -1; } database += ".db"; //remote mode if (gc != NULL) { if (gc->load(database)) { return 0; } else { return -1; } } if (current_database != NULL) { cerr << "Please unload your database first!" << endl; return -1; } current_database = new Database(database); bool flag = current_database->load(); if (!flag) { cerr << "Failed to load the database." << endl; delete current_database; current_database = NULL; return -1; } cout << "Database loaded successfully." << endl; return 0; } int unload_handler(const vector& args) { if (!args.empty()) { cerr << "Too many arguments!" << endl; return -1; } //remote mode if (gc != NULL) { string database = gc->show(); if (database == "\n[empty]\n") { cerr << "No database used now." << endl; return -1; } if (gc->unload(database.substr(1, database.length() - 2))) { return 0; } else { return -1; } } if (current_database == NULL) { cerr << "No database used now." << endl; return -1; } delete current_database; current_database = NULL; cout << "Database unloaded." << endl; return 0; } int query_handler(const vector& args) { if (args.size() != 1) { cerr << "Exactly 1 argument required!" << endl; return -1; } if (current_database == NULL) { cerr << "No database in use!" << endl; return -1; } string sparql; if (args[0][0] == '\"') { //query quoted in string sparql = args[0].substr(1, args[0].length() - 2); } else { //query in file indicated by this path //NOTICE:the query is native, not in server! string ret = Util::getExactPath(args[0].c_str()); const char *path = ret.c_str(); if (path == NULL) { cerr << "Invalid path of query." << endl; return -1; } #ifdef DEBUG cout << path << endl; #endif sparql = Util::getQueryFromFile(path); } if (sparql.empty()) { cerr << "Empty SPARQL." << endl; return -1; } #ifdef DEBUG cout << sparql << endl; #endif //remote mode if (gc != NULL) { //QUERY:how to use query path in the server //execute SPARQL query on this database. string answer = gc->query(sparql); fprintf(output, "%s\n", answer.c_str()); return 0; } ResultSet rs; bool ret = current_database->query(sparql, rs, output); if (ret) { #ifdef DEBUG_PRECISE cout << "query() returns true!" << endl; #endif return 0; } else { #ifdef DEBUG cout << "query() returns false!" << endl; #endif return -1; } } int start_handler(const vector& args) { if (args.size() > 1) { cerr << "Too many arguments!" << endl; return -1; } unsigned short port = GstoreConnector::defaultServerPort; if (!args.empty()) { if (Util::isValidPort(args[0])) { stringstream(args[0]) >> port; } else { cerr << "Invalid port: " << args[0] << endl; return -1; } } static const int max = 20; // max length of time string char time_str[max]; time_t timep; time(&timep); strftime(time_str, max, "%Y%m%d_%H%M%S_", gmtime(&timep)); string gserver_path = "bin/gserver"; stringstream ss; ss << "logs/gserver_" << time_str << port; string log_path = ss.str(); ss.str(string()); ss << gserver_path << ' ' << port << " >> " << log_path << " 2>&1 &"; string cmd = ss.str(); Util::create_dir("logs"); system(cmd.c_str()); cout << "Local gserve started at port " << port << '.' << endl; cout << "Output redirected to: " << log_path << endl; return 0; } int stop_handler(const vector& args) { if (!args.empty()) { cerr << "Too many arguments!" << endl; return -1; } if (gc == NULL) { cerr << "This command cannot be used when in native mode." << endl; return -1; } if (!gc->stop()) { return -1; } delete gc; gc = NULL; current_commands = native_commands; cout << "Now is in native mode, please type your commands." << endl; return 0; }