settings manager: better default setting handling and updating config file and comman...
authorPerttu Ahola <celeron55@gmail.com>
Tue, 14 Dec 2010 13:16:49 +0000 (15:16 +0200)
committerPerttu Ahola <celeron55@gmail.com>
Tue, 14 Dec 2010 13:16:49 +0000 (15:16 +0200)
doc/README.txt
minetest.conf.example
src/exceptions.h
src/main.cpp
src/socket.cpp
src/test.cpp
src/utility.h

index 0cb31da3886e4762e1e11bdc4ede4fe59f41a764..3467564bd1ed88382696119d2966b45144993c56 100644 (file)
@@ -32,7 +32,8 @@ Controls:
 
 Configuration file:
 - An optional configuration file can be used. See minetest.conf.example.
-- Path to file can be passed as a parameter to the executable.
+- Path to file can be passed as a parameter to the executable:
+       --config <path-to-file>
 - If not given as a parameter, these are checked, in order:
        ../minetest.conf
        ../../minetest.conf
index 21ce1072160fc26cf447b95c3dba52fcf973189c..aa38c8c7a7dbd6394d75155994cc3017d219b54b 100644 (file)
@@ -7,8 +7,6 @@
 # By default, all the settings are commented and not functional.
 # Uncomment settings by removing the preceding #.
 
-#dedicated_server =
-
 # Client side stuff
 
 #wanted_fps = 30
index 0f95bd07a9de1e42d37f53ec409f48003c09b4d8..95b9eea97ee6ba5e70bf2737047e8cbef7f6ae15 100644 (file)
@@ -124,6 +124,14 @@ public:
        {}
 };
 
+class CommandLineError : public BaseException
+{
+public:
+       CommandLineError(const char *s):
+               BaseException(s)
+       {}
+};
+
 /*
        Some "old-style" interrupts:
 */
index 7814da999c27420ce3391bec6144c55cc2db475b..b4e2d478c1075a77086c0c225b45339b28de373c 100644 (file)
@@ -306,38 +306,36 @@ Settings g_settings;
 // Sets default settings\r
 void set_default_settings()\r
 {\r
-       g_settings.set("dedicated_server", "");\r
-\r
        // Client stuff\r
-       g_settings.set("wanted_fps", "30");\r
-       g_settings.set("fps_max", "60");\r
-       g_settings.set("viewing_range_nodes_max", "300");\r
-       g_settings.set("viewing_range_nodes_min", "35");\r
-       g_settings.set("screenW", "");\r
-       g_settings.set("screenH", "");\r
-       g_settings.set("host_game", "");\r
-       g_settings.set("port", "");\r
-       g_settings.set("address", "");\r
-       g_settings.set("name", "");\r
-       g_settings.set("random_input", "false");\r
-       g_settings.set("client_delete_unused_sectors_timeout", "1200");\r
-       g_settings.set("max_block_send_distance", "8");\r
-       g_settings.set("max_block_generate_distance", "6");\r
+       g_settings.setDefault("wanted_fps", "30");\r
+       g_settings.setDefault("fps_max", "60");\r
+       g_settings.setDefault("viewing_range_nodes_max", "300");\r
+       g_settings.setDefault("viewing_range_nodes_min", "35");\r
+       g_settings.setDefault("screenW", "");\r
+       g_settings.setDefault("screenH", "");\r
+       g_settings.setDefault("host_game", "");\r
+       g_settings.setDefault("port", "");\r
+       g_settings.setDefault("address", "");\r
+       g_settings.setDefault("name", "");\r
+       g_settings.setDefault("random_input", "false");\r
+       g_settings.setDefault("client_delete_unused_sectors_timeout", "1200");\r
+       g_settings.setDefault("max_block_send_distance", "8");\r
+       g_settings.setDefault("max_block_generate_distance", "6");\r
 \r
        // Server stuff\r
-       g_settings.set("creative_mode", "false");\r
-       g_settings.set("heightmap_blocksize", "32");\r
-       g_settings.set("height_randmax", "constant 50.0");\r
-       g_settings.set("height_randfactor", "constant 0.6");\r
-       g_settings.set("height_base", "linear 0 0 0");\r
-       g_settings.set("plants_amount", "1.0");\r
-       g_settings.set("ravines_amount", "1.0");\r
-       g_settings.set("objectdata_interval", "0.2");\r
-       g_settings.set("active_object_range", "2");\r
-       g_settings.set("max_simultaneous_block_sends_per_client", "1");\r
-       g_settings.set("max_simultaneous_block_sends_server_total", "4");\r
-       g_settings.set("disable_water_climb", "true");\r
-       g_settings.set("endless_water", "true");\r
+       g_settings.setDefault("creative_mode", "false");\r
+       g_settings.setDefault("heightmap_blocksize", "32");\r
+       g_settings.setDefault("height_randmax", "constant 50.0");\r
+       g_settings.setDefault("height_randfactor", "constant 0.6");\r
+       g_settings.setDefault("height_base", "linear 0 0 0");\r
+       g_settings.setDefault("plants_amount", "1.0");\r
+       g_settings.setDefault("ravines_amount", "1.0");\r
+       g_settings.setDefault("objectdata_interval", "0.2");\r
+       g_settings.setDefault("active_object_range", "2");\r
+       g_settings.setDefault("max_simultaneous_block_sends_per_client", "1");\r
+       g_settings.setDefault("max_simultaneous_block_sends_server_total", "4");\r
+       g_settings.setDefault("disable_water_climb", "true");\r
+       g_settings.setDefault("endless_water", "true");\r
 }\r
 \r
 /*\r
@@ -962,6 +960,51 @@ int main(int argc, char *argv[])
        try\r
        {\r
        \r
+       /*\r
+               Parse command line\r
+               TODO\r
+       */\r
+       \r
+       core::map<std::string, ValueSpec> allowed_options;\r
+       allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));\r
+       allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,\r
+                       "Run server directly"));\r
+       allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,\r
+                       "Load configuration from specified file"));\r
+       allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));\r
+\r
+       Settings cmd_args;\r
+       \r
+       bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);\r
+\r
+       if(ret == false || cmd_args.getFlag("help"))\r
+       {\r
+               dstream<<"Allowed options:"<<std::endl;\r
+               for(core::map<std::string, ValueSpec>::Iterator\r
+                               i = allowed_options.getIterator();\r
+                               i.atEnd() == false; i++)\r
+               {\r
+                       dstream<<"  --"<<i.getNode()->getKey();\r
+                       if(i.getNode()->getValue().type == VALUETYPE_FLAG)\r
+                       {\r
+                       }\r
+                       else\r
+                       {\r
+                               dstream<<" <value>";\r
+                       }\r
+                       dstream<<std::endl;\r
+\r
+                       if(i.getNode()->getValue().help != NULL)\r
+                       {\r
+                               dstream<<"      "<<i.getNode()->getValue().help\r
+                                               <<std::endl;\r
+                       }\r
+               }\r
+\r
+               return cmd_args.getFlag("help") ? 0 : 1;\r
+       }\r
+\r
+\r
        /*\r
                Basic initialization\r
        */\r
@@ -999,11 +1042,23 @@ int main(int argc, char *argv[])
                Initialization\r
        */\r
 \r
-       // Read config file\r
+       /*\r
+               Read config file\r
+       */\r
        \r
-       if(argc >= 2)\r
+       // Path of configuration file in use\r
+       std::string configpath = "";\r
+       \r
+       if(cmd_args.exists("config"))\r
        {\r
-               g_settings.readConfigFile(argv[1]);\r
+               bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());\r
+               if(r == false)\r
+               {\r
+                       dstream<<"Could not read configuration from \""\r
+                                       <<cmd_args.get("config")<<"\""<<std::endl;\r
+                       return 1;\r
+               }\r
+               configpath = cmd_args.get("config");\r
        }\r
        else\r
        {\r
@@ -1017,7 +1072,10 @@ int main(int argc, char *argv[])
                {\r
                        bool r = g_settings.readConfigFile(filenames[i]);\r
                        if(r)\r
+                       {\r
+                               configpath = filenames[i];\r
                                break;\r
+                       }\r
                }\r
        }\r
 \r
@@ -1059,16 +1117,19 @@ int main(int argc, char *argv[])
        std::cout<<std::endl;\r
        char templine[100];\r
        \r
-       // Dedicated?\r
-       bool dedicated = g_settings.getBoolAsk\r
-                       ("dedicated_server", "Dedicated server?", false);\r
-       std::cout<<"dedicated = "<<dedicated<<std::endl;\r
-       \r
        // Port?\r
-       u16 port = g_settings.getU16Ask("port", "Port", 30000);\r
-       std::cout<<"-> "<<port<<std::endl;\r
+       u16 port = 30000;\r
+       if(cmd_args.exists("port"))\r
+       {\r
+               port = cmd_args.getU16("port");\r
+       }\r
+       else\r
+       {\r
+               port = g_settings.getU16Ask("port", "Port", 30000);\r
+               std::cout<<"-> "<<port<<std::endl;\r
+       }\r
        \r
-       if(dedicated)\r
+       if(cmd_args.getFlag("server"))\r
        {\r
                DSTACK("Dedicated server branch");\r
                \r
@@ -2208,7 +2269,8 @@ int main(int argc, char *argv[])
                \r
                {\r
                TimeTaker timer("beginScene", device);\r
-               driver->beginScene(true, true, bgcolor);\r
+               //driver->beginScene(true, true, bgcolor);\r
+               driver->beginScene(false, true, bgcolor);\r
                beginscenetime = timer.stop(true);\r
                }\r
 \r
@@ -2306,6 +2368,14 @@ int main(int argc, char *argv[])
                In the end, delete the Irrlicht device.\r
        */\r
        device->drop();\r
+       \r
+       /*\r
+               Update configuration file\r
+       */\r
+       if(configpath != "")\r
+       {\r
+               g_settings.updateConfigFile(configpath.c_str());\r
+       }\r
 \r
        } //try\r
        catch(con::PeerNotFoundException &e)\r
index 9ff30e10ff487678f4f270f45080f37d46ead77f..f4b8f442943aaf78834569547ab376763a65aef0 100644 (file)
@@ -205,7 +205,7 @@ void UDPSocket::Send(const Address & destination, const void * data, int size)
                destination.print();
                dstream<<", size="<<size<<", data=";
                for(int i=0; i<size && i<20; i++){
-                       if(i%2==0) printf(" ");
+                       if(i%2==0) DEBUGPRINT(" ");
                        DEBUGPRINT("%.2X", ((int)((const char*)data)[i])&0xff);
                }
                if(size>20)
@@ -267,7 +267,7 @@ int UDPSocket::Receive(Address & sender, void * data, int size)
                //dstream<<", received="<<received<<std::endl;
                dstream<<", size="<<received<<", data=";
                for(int i=0; i<received && i<20; i++){
-                       if(i%2==0) printf(" ");
+                       if(i%2==0) DEBUGPRINT(" ");
                        DEBUGPRINT("%.2X", ((int)((const char*)data)[i])&0xff);
                }
                if(received>20)
index ce7540e8d7e1fd7b4366007c17276873766a3e8d..313fe50db3f4a5fc1b158cad8ffa657891487ba5 100644 (file)
@@ -1070,8 +1070,8 @@ struct TestConnection
 
                        dstream<<"Sending data (size="<<1100<<"):";
                        for(int i=0; i<1100 && i<20; i++){
-                               if(i%2==0) printf(" ");
-                               printf("%.2X", ((int)((const char*)*data1)[i])&0xff);
+                               if(i%2==0) DEBUGPRINT(" ");
+                               DEBUGPRINT("%.2X", ((int)((const char*)*data1)[i])&0xff);
                        }
                        if(1100>20)
                                dstream<<"...";
@@ -1091,8 +1091,8 @@ struct TestConnection
 
                        dstream<<"Received data (size="<<size<<"):";
                        for(int i=0; i<size && i<20; i++){
-                               if(i%2==0) printf(" ");
-                               printf("%.2X", ((int)((const char*)recvdata)[i])&0xff);
+                               if(i%2==0) DEBUGPRINT(" ");
+                               DEBUGPRINT("%.2X", ((int)((const char*)recvdata)[i])&0xff);
                        }
                        if(size>20)
                                dstream<<"...";
index afcb4e414ae024b455d2a7c24ebf5f28050dca90..f7e726f87b389418420e5b7714197a40543b9cd5 100644 (file)
@@ -668,6 +668,23 @@ inline s32 stoi(std::string s)
        Config stuff
 */
 
+enum ValueType
+{
+       VALUETYPE_STRING,
+       VALUETYPE_FLAG // Doesn't take any arguments
+};
+
+struct ValueSpec
+{
+       ValueSpec(ValueType a_type, const char *a_help=NULL)
+       {
+               type = a_type;
+               help = a_help;
+       }
+       ValueType type;
+       const char *help;
+};
+
 class Settings
 {
 public:
@@ -710,36 +727,255 @@ public:
                return true;
        }
 
-       // Returns true on success
+       /*
+               Read configuration file
+
+               Returns true on success
+       */
        bool readConfigFile(const char *filename)
        {
                std::ifstream is(filename);
                if(is.good() == false)
                {
-                       dstream<<"Error opening configuration file"
-                                       <<filename<<std::endl;
+                       dstream<<"Error opening configuration file \""
+                                       <<filename<<"\""<<std::endl;
                        return false;
                }
 
-               dstream<<"Parsing configuration file: "
-                               <<filename<<std::endl;
+               dstream<<"Parsing configuration file: \""
+                               <<filename<<"\""<<std::endl;
                                
                while(parseConfigObject(is));
                
                return true;
        }
 
+       /*
+               Reads a configuration object from stream (usually a single line)
+               and adds it to dst.
+               
+               Preserves comments and empty lines.
+
+               Settings that were added to dst are also added to updated.
+               key of updated is setting name, value of updated is dummy.
+
+               Returns false on EOF
+       */
+       bool getUpdatedConfigObject(std::istream &is,
+                       core::list<std::string> &dst,
+                       core::map<std::string, bool> &updated)
+       {
+               if(is.eof())
+                       return false;
+               
+               // NOTE: This function will be expanded to allow multi-line settings
+               std::string line;
+               std::getline(is, line);
+
+               std::string trimmedline = trim(line);
+
+               std::string line_end = "";
+               if(is.eof() == false)
+                       line_end = "\n";
+               
+               // Ignore comments
+               if(trimmedline[0] == '#')
+               {
+                       dst.push_back(line+line_end);
+                       return true;
+               }
+
+               Strfnd sf(trim(line));
+
+               std::string name = sf.next("=");
+               name = trim(name);
+
+               if(name == "")
+               {
+                       dst.push_back(line+line_end);
+                       return true;
+               }
+               
+               std::string value = sf.next("\n");
+               value = trim(value);
+               
+               if(m_settings.find(name))
+               {
+                       std::string newvalue = m_settings[name];
+                       
+                       if(newvalue != value)
+                       {
+                               dstream<<"Changing value of \""<<name<<"\" = \""
+                                               <<value<<"\" -> \""<<newvalue<<"\""
+                                               <<std::endl;
+                       }
+
+                       dst.push_back(name + " = " + newvalue + line_end);
+
+                       updated[name] = true;
+               }
+               
+               return true;
+       }
+
+       /*
+               Updates configuration file
+
+               Returns true on success
+       */
+       bool updateConfigFile(const char *filename)
+       {
+               dstream<<"Updating configuration file: \""
+                               <<filename<<"\""<<std::endl;
+               
+               core::list<std::string> objects;
+               core::map<std::string, bool> updated;
+               
+               // Read and modify stuff
+               {
+                       std::ifstream is(filename);
+                       if(is.good() == false)
+                       {
+                               dstream<<"Error opening configuration file"
+                                               " for reading: \""
+                                               <<filename<<"\""<<std::endl;
+                               return false;
+                       }
+
+                       while(getUpdatedConfigObject(is, objects, updated));
+               }
+               
+               // Write stuff back
+               {
+                       std::ofstream os(filename);
+                       if(os.good() == false)
+                       {
+                               dstream<<"Error opening configuration file"
+                                               " for writing: \""
+                                               <<filename<<"\""<<std::endl;
+                               return false;
+                       }
+                       
+                       /*
+                               Write updated stuff
+                       */
+                       for(core::list<std::string>::Iterator
+                                       i = objects.begin();
+                                       i != objects.end(); i++)
+                       {
+                               os<<(*i);
+                       }
+
+                       /*
+                               Write stuff that was not already in the file
+                       */
+                       for(core::map<std::string, std::string>::Iterator
+                                       i = m_settings.getIterator();
+                                       i.atEnd() == false; i++)
+                       {
+                               if(updated.find(i.getNode()->getKey()))
+                                       continue;
+                               std::string name = i.getNode()->getKey();
+                               std::string value = i.getNode()->getValue();
+                               dstream<<"Adding \""<<name<<"\" = \""<<value<<"\""
+                                               <<std::endl;
+                               os<<name<<" = "<<value<<"\n";
+                       }
+               }
+               
+               return true;
+       }
+
+       /*
+               NOTE: Types of allowed_options are ignored
+
+               returns true on success
+       */
+       bool parseCommandLine(int argc, char *argv[],
+                       core::map<std::string, ValueSpec> &allowed_options)
+       {
+               int i=1;
+               for(;;)
+               {
+                       if(i >= argc)
+                               break;
+                       std::string argname = argv[i];
+                       if(argname.substr(0, 2) != "--")
+                       {
+                               dstream<<"Invalid command-line parameter \""
+                                               <<argname<<"\": --<option> expected."<<std::endl;
+                               return false;
+                       }
+                       i++;
+
+                       std::string name = argname.substr(2);
+
+                       core::map<std::string, ValueSpec>::Node *n;
+                       n = allowed_options.find(name);
+                       if(n == NULL)
+                       {
+                               dstream<<"Unknown command-line parameter \""
+                                               <<argname<<"\""<<std::endl;
+                               return false;
+                       }
+
+                       ValueType type = n->getValue().type;
+
+                       std::string value = "";
+                       
+                       if(type == VALUETYPE_FLAG)
+                       {
+                               value = "true";
+                       }
+                       else
+                       {
+                               if(i >= argc)
+                               {
+                                       dstream<<"Invalid command-line parameter \""
+                                                       <<name<<"\": missing value"<<std::endl;
+                                       return false;
+                               }
+                               value = argv[i];
+                               i++;
+                       }
+                       
+
+                       dstream<<"Valid command-line parameter: \""
+                                       <<name<<"\" = \""<<value<<"\""
+                                       <<std::endl;
+                       set(name, value);
+               }
+
+               return true;
+       }
+
        void set(std::string name, std::string value)
        {
                m_settings[name] = value;
        }
 
+       void setDefault(std::string name, std::string value)
+       {
+               m_defaults[name] = value;
+       }
+
+       bool exists(std::string name)
+       {
+               return (m_settings.find(name) || m_defaults.find(name));
+       }
+
        std::string get(std::string name)
        {
                core::map<std::string, std::string>::Node *n;
                n = m_settings.find(name);
                if(n == NULL)
-                       throw SettingNotFoundException("Setting not found");
+               {
+                       n = m_defaults.find(name);
+                       if(n == NULL)
+                       {
+                               throw SettingNotFoundException("Setting not found");
+                       }
+               }
 
                return n->getValue();
        }
@@ -749,6 +985,18 @@ public:
                return is_yes(get(name));
        }
        
+       bool getFlag(std::string name)
+       {
+               try
+               {
+                       return getBool(name);
+               }
+               catch(SettingNotFoundException &e)
+               {
+                       return false;
+               }
+       }
+
        // Asks if empty
        bool getBoolAsk(std::string name, std::string question, bool def)
        {
@@ -809,6 +1057,7 @@ public:
 
 private:
        core::map<std::string, std::string> m_settings;
+       core::map<std::string, std::string> m_defaults;
 };
 
 /*