version 0.3.33
[fms.git] / src / fmsapp.cpp
index 810bd5f..89bb9d7 100644 (file)
@@ -9,12 +9,16 @@
 #include "../include/dbmaintenancethread.h"\r
 #include "../include/freenet/freenetmasterthread.h"\r
 #include "../include/threadwrapper/threadedexecutor.h"\r
+#include "../include/db/sqlite3db.h"\r
 \r
 #include <Poco/Util/HelpFormatter.h>\r
 #include <Poco/FileChannel.h>\r
+#include <Poco/ConsoleChannel.h>\r
 #include <Poco/FormattingChannel.h>\r
 #include <Poco/PatternFormatter.h>\r
 #include <iostream>\r
+#include <string>\r
+#include <cstring>\r
 \r
 #ifdef _WIN32\r
        #include <direct.h>\r
        #include <unistd.h>\r
 #endif\r
 \r
-//debug\r
-#include <Poco/ScopedLock.h>\r
-#include <Poco/Runnable.h>\r
-#include <Poco/Thread.h>\r
-#include <Poco/ThreadPool.h>\r
-#include "../include/threadwrapper/cancelablerunnable.h"\r
-#include "../include/threadwrapper/cancelablethread.h"\r
+#ifdef FROST_SUPPORT\r
+       #include <tommath.h>\r
+       #include <tomcrypt.h>\r
+#endif\r
 \r
-Poco::FastMutex m1;\r
-class TestRunner:public CancelableRunnable\r
+FMSApp::FMSApp():m_displayhelp(false),m_showoptions(false),m_setoption(false),m_logtype("file"),m_workingdirectory("")\r
 {\r
-public:\r
-       void run()\r
+       // get current working dir so we can go to it later\r
+       char wd[1024];\r
+       char *wdptr=NULL;\r
+       memset(wd,0,1024);\r
+       wdptr=getcwd(wd,1023);\r
+       if(wdptr)\r
        {\r
-               do\r
-               {\r
-\r
-               }while(!IsCancelled());\r
+               m_workingdirectory=wdptr;\r
        }\r
-};\r
-\r
-FMSApp::FMSApp():m_displayhelp(false)\r
-{\r
-\r
 }\r
 \r
 void FMSApp::defineOptions(Poco::Util::OptionSet &options)\r
@@ -53,7 +49,10 @@ void FMSApp::defineOptions(Poco::Util::OptionSet &options)
        ServerApplication::defineOptions(options);\r
 \r
        // add command line options here\r
-       options.addOption(Poco::Util::Option("help","?","display help for command line arguments",false).repeatable(false).callback(Poco::Util::OptionCallback<FMSApp>(this,&FMSApp::handleHelp)));\r
+       options.addOption(Poco::Util::Option("help","?","Display help for command line arguments.",false).repeatable(false).callback(Poco::Util::OptionCallback<FMSApp>(this,&FMSApp::handleHelp)));\r
+       options.addOption(Poco::Util::Option("log","l","Select type of log output (file|stdout|stderr).",false,"type",true).repeatable(false).callback(Poco::Util::OptionCallback<FMSApp>(this,&FMSApp::handleLogOption)));\r
+       options.addOption(Poco::Util::Option("showoptions","","Show all options that can be set and their current values.",false).repeatable(false).callback(Poco::Util::OptionCallback<FMSApp>(this,&FMSApp::handleShowOptions)));\r
+       options.addOption(Poco::Util::Option("setoption","","Set an option.  Values are not validated, so be sure to set them correctly.",false,"option=value",true).repeatable(true).callback(Poco::Util::OptionCallback<FMSApp>(this,&FMSApp::handleSetOption)));\r
 }\r
 \r
 void FMSApp::displayHelp()\r
@@ -72,15 +71,65 @@ void FMSApp::handleHelp(const std::string &name, const std::string &value)
        stopOptionsProcessing();\r
 }\r
 \r
+void FMSApp::handleLogOption(const std::string &name, const std::string &value)\r
+{\r
+       if(value=="file" || value=="stdout" || value=="stderr")\r
+       {\r
+               m_logtype=value;\r
+       }\r
+}\r
+\r
+void FMSApp::handleSetOption(const std::string &name, const std::string &value)\r
+{\r
+       std::vector<std::string> valueparts;\r
+       StringFunctions::Split(value,"=",valueparts);\r
+\r
+       if(valueparts.size()==2)\r
+       {\r
+               m_setoptions[valueparts[0]]=valueparts[1];\r
+       }\r
+       else\r
+       {\r
+               std::cout << "Expected option=value but found " << value << std::endl;\r
+       }\r
+\r
+       m_setoption=true;\r
+}\r
+\r
+void FMSApp::handleShowOptions(const std::string &name, const std::string &value)\r
+{\r
+       m_showoptions=true;\r
+}\r
+\r
 void FMSApp::initialize(Poco::Util::Application &self)\r
 {\r
        ServerApplication::initialize(self);\r
 \r
-       // set working directory to program directory\r
-       int rval=chdir(config().getString("application.dir").c_str());\r
+       // set working directory - fall back on application.dir if working directory isn't set\r
+       // if we are runing as a service, then working directory needs to be set to the application directory\r
+       if(m_workingdirectory=="" || config().getBool("application.runAsService",false)==true)\r
+       {\r
+               m_workingdirectory=config().getString("application.dir");\r
+       }\r
+       int rval=chdir(m_workingdirectory.c_str());\r
+\r
+#ifdef QUERY_LOG\r
+       {\r
+               Poco::AutoPtr<Poco::FormattingChannel> formatter=new Poco::FormattingChannel(new Poco::PatternFormatter("%Y-%m-%d %H:%M:%S | %t"));\r
+               Poco::AutoPtr<Poco::FileChannel> fc=new Poco::FileChannel("query.log");\r
+               fc->setProperty("rotation","daily");\r
+               fc->setProperty("times","utc");\r
+               fc->setProperty("archive","timestamp");\r
+               fc->setProperty("purgeCount","5");\r
+               fc->setProperty("compress","true");\r
+               formatter->setChannel(fc);\r
+               Poco::Logger::create("querylog",formatter,Poco::Message::PRIO_INFORMATION);\r
+       }\r
+#endif\r
 \r
-       SetupDB();\r
-       SetupDefaultOptions();\r
+       LoadDatabase();\r
+       SetupDB(m_db);\r
+       SetupDefaultOptions(m_db);\r
        initializeLogger();\r
        config().setString("application.logger","logfile");\r
 }\r
@@ -90,35 +139,104 @@ void FMSApp::initializeLogger()
        int initiallevel=Poco::Message::PRIO_TRACE;\r
 \r
        std::string tempval="";\r
-       if(Option::Instance()->Get("LogLevel",tempval))\r
+       Option option(m_db);\r
+\r
+       if(option.Get("LogLevel",tempval))\r
        {\r
                StringFunctions::Convert(tempval,initiallevel);\r
        }\r
 \r
        Poco::AutoPtr<Poco::FormattingChannel> formatter=new Poco::FormattingChannel(new Poco::PatternFormatter("%Y-%m-%d %H:%M:%S | %p | %t"));\r
-       Poco::AutoPtr<Poco::FileChannel> fc=new Poco::FileChannel("fms.log");\r
-       fc->setProperty("rotation","daily");    // rotate log file daily\r
-       fc->setProperty("times","utc");                 // utc date/times for log entries\r
-       fc->setProperty("archive","timestamp"); // add timestamp to old logs\r
-       fc->setProperty("purgeCount","30");             // purge old logs after 30 logs have accumulated\r
-       fc->setProperty("compress","true");             // gz compress old log files\r
-       formatter->setChannel(fc);\r
        \r
-       setLogger(Poco::Logger::create("logfile",formatter,initiallevel));\r
+       if(m_logtype=="file")\r
+       {\r
+               Poco::AutoPtr<Poco::FileChannel> fc=new Poco::FileChannel("fms.log");\r
+               fc->setProperty("rotation","daily");    // rotate log file daily\r
+               fc->setProperty("times","utc");                 // utc date/times for log entries\r
+               fc->setProperty("archive","timestamp"); // add timestamp to old logs\r
+               fc->setProperty("purgeCount","30");             // purge old logs after 30 logs have accumulated\r
+               fc->setProperty("compress","true");             // gz compress old log files\r
+               formatter->setChannel(fc);\r
+       }\r
+       else\r
+       {\r
+               if(m_logtype=="stdout")\r
+               {\r
+                       Poco::AutoPtr<Poco::ConsoleChannel> cc=new Poco::ConsoleChannel(std::cout);\r
+                       formatter->setChannel(cc);\r
+               }\r
+               else\r
+               {\r
+                       Poco::AutoPtr<Poco::ConsoleChannel> cc=new Poco::ConsoleChannel(std::cerr);\r
+                       formatter->setChannel(cc);\r
+               }\r
+       }\r
+       \r
+       setLogger(Poco::Logger::create("logfile",formatter,Poco::Message::PRIO_INFORMATION));\r
+       Poco::Logger::get("logfile").information("LogLevel set to "+tempval);\r
+       Poco::Logger::get("logfile").setLevel(initiallevel);\r
+\r
 }\r
 \r
 int FMSApp::main(const std::vector<std::string> &args)\r
 {\r
 \r
-       if(m_displayhelp==false)\r
+       // running as a daemon would reset the working directory to / before calling main\r
+       // so we need to set the working directory again\r
+       int rval=chdir(m_workingdirectory.c_str());\r
+\r
+       if(VerifyDB(m_db)==false)\r
+       {\r
+               std::cout << "The FMS database failed verification.  It is most likely corrupt!" << std::endl;\r
+               logger().fatal("The FMS database failed verification.  It is most likely corrupt!");\r
+       }\r
+       else if(m_displayhelp)\r
+       {\r
+       }\r
+       else if(m_showoptions)\r
+       {\r
+               showOptions();\r
+       }\r
+       else if(m_setoption)\r
+       {\r
+               setOptions();\r
+       }\r
+       else\r
        {\r
                logger().information("FMS startup v"FMS_VERSION);\r
+               logger().information("Using SQLite "SQLITE_VERSION);\r
+\r
+               std::string tempval("");\r
+               Option option(m_db);\r
+               option.Get("VacuumOnStartup",tempval);\r
+               if(tempval=="true")\r
+               {\r
+                       logger().information("VACUUMing database");\r
+                       m_db->Execute("VACUUM;");\r
+               }\r
+\r
+#ifdef FROST_SUPPORT\r
+               ltc_mp=ltm_desc;\r
+               register_hash(&sha1_desc);\r
+#endif\r
 \r
                StartThreads();\r
 \r
+               if(isInteractive()==true)\r
+               {\r
+                       std::cout << "FMS has been started." << std::endl << std::endl;\r
+               }\r
+\r
                waitForTerminationRequest();\r
 \r
+               if(isInteractive()==true)\r
+               {\r
+                       std::cout << "Please wait while FMS shuts down." << std::endl << std::endl;\r
+               }\r
+\r
+               logger().trace("FMSApp::main cancelling threads");\r
                m_threads.Cancel();\r
+               logger().trace("FMSApp::main joining threads");\r
                m_threads.Join();\r
 \r
                logger().information("FMS shutdown");\r
@@ -127,31 +245,95 @@ int FMSApp::main(const std::vector<std::string> &args)
        return FMSApp::EXIT_OK;\r
 }\r
 \r
+void FMSApp::setOptions()\r
+{\r
+       for(std::map<std::string,std::string>::iterator i=m_setoptions.begin(); i!=m_setoptions.end(); i++)\r
+       {\r
+               std::string tempval("");\r
+               Option option(m_db);\r
+               if(option.Get((*i).first,tempval))\r
+               {\r
+                       option.Set((*i).first,(*i).second);\r
+                       std::cout << "Option " << (*i).first << " set to " << (*i).second << std::endl;\r
+               }\r
+               else\r
+               {\r
+                       std::cout << "Unknown option " << (*i).first << std::endl;\r
+               }\r
+       }\r
+}\r
+\r
+void FMSApp::showOptions()\r
+{\r
+       SQLite3DB::Statement st=m_db->Prepare("SELECT Option, OptionValue FROM tblOption;");\r
+       st.Step();\r
+       while(st.RowReturned())\r
+       {\r
+               std::string option="";\r
+               std::string optionvalue="";\r
+               st.ResultText(0,option);\r
+               st.ResultText(1,optionvalue);\r
+\r
+               std::cout << option << " = " << optionvalue << std::endl;\r
+\r
+               st.Step();\r
+       }\r
+}\r
+\r
 void FMSApp::StartThreads()\r
 {\r
-       std::string tempval="";\r
+       std::string tempval("");\r
+       Option option(m_db);\r
 \r
        // always start the DB maintenance thread\r
+       logger().trace("FMSApp::StartThreads starting DBMaintenanceThread");\r
        m_threads.Start(new DBMaintenanceThread());\r
 \r
-       Option::Instance()->Get("StartHTTP",tempval);\r
+       option.Get("StartHTTP",tempval);\r
        if(tempval=="true")\r
        {\r
+               logger().trace("FMSApp::StartThreads starting HTTPThread");\r
                m_threads.Start(new HTTPThread());\r
+               if(isInteractive())\r
+               {\r
+                       std::cout << "Started HTTP Thread" << std::endl;\r
+               }\r
+       }\r
+       else\r
+       {\r
+               logger().trace("FMSApp::StartThreads not starting HTTPThread");\r
        }\r
 \r
        tempval="";\r
-       Option::Instance()->Get("StartNNTP",tempval);\r
+       option.Get("StartNNTP",tempval);\r
        if(tempval=="true")\r
        {\r
+               logger().trace("FMSApp::StartThreads starting NNTPListener");\r
                m_threads.Start(new NNTPListener());\r
+               if(isInteractive())\r
+               {\r
+                       std::cout << "Started NNTP Thread" << std::endl;\r
+               }\r
+       }\r
+       else\r
+       {\r
+               logger().trace("FMSApp::StartThreads not starting NNTPListener");\r
        }\r
 \r
        tempval="";\r
-       Option::Instance()->Get("StartFreenetUpdater",tempval);\r
+       option.Get("StartFreenetUpdater",tempval);\r
        if(tempval=="true")\r
        {\r
+               logger().trace("FMSApp::StartThreads starting FreenetMasterThread");\r
                m_threads.Start(new FreenetMasterThread());\r
+               if(isInteractive())\r
+               {\r
+                       std::cout << "Started Freenet FCP Thread" << std::endl;\r
+               }\r
+       }\r
+       else\r
+       {\r
+               logger().trace("FMSApp::StartThreads not starting FreenetMasterThread");\r
        }\r
 \r
 }\r