version 0.3.29
[fms.git] / src / fmsapp.cpp
1 #include "../include/fmsapp.h"\r
2 #include "../include/global.h"\r
3 #include "../include/dbsetup.h"\r
4 #include "../include/optionssetup.h"\r
5 #include "../include/option.h"\r
6 #include "../include/stringfunctions.h"\r
7 #include "../include/http/httpthread.h"\r
8 #include "../include/nntp/nntplistener.h"\r
9 #include "../include/dbmaintenancethread.h"\r
10 #include "../include/freenet/freenetmasterthread.h"\r
11 #include "../include/threadwrapper/threadedexecutor.h"\r
12 #include "../include/db/sqlite3db.h"\r
13 \r
14 #include <Poco/Util/HelpFormatter.h>\r
15 #include <Poco/FileChannel.h>\r
16 #include <Poco/ConsoleChannel.h>\r
17 #include <Poco/FormattingChannel.h>\r
18 #include <Poco/PatternFormatter.h>\r
19 #include <iostream>\r
20 #include <string>\r
21 #include <cstring>\r
22 \r
23 #ifdef _WIN32\r
24         #include <direct.h>\r
25 #else\r
26         #include <unistd.h>\r
27 #endif\r
28 \r
29 FMSApp::FMSApp():m_displayhelp(false),m_showoptions(false),m_setoption(false),m_logtype("file"),m_workingdirectory("")\r
30 {\r
31         // get current working dir so we can go to it later\r
32         char wd[1024];\r
33         char *wdptr=NULL;\r
34         memset(wd,0,1024);\r
35         wdptr=getcwd(wd,1023);\r
36         if(wdptr)\r
37         {\r
38                 m_workingdirectory=wdptr;\r
39         }\r
40 }\r
41 \r
42 void FMSApp::defineOptions(Poco::Util::OptionSet &options)\r
43 {\r
44         ServerApplication::defineOptions(options);\r
45 \r
46         // add command line options here\r
47         options.addOption(Poco::Util::Option("help","?","Display help for command line arguments.",false).repeatable(false).callback(Poco::Util::OptionCallback<FMSApp>(this,&FMSApp::handleHelp)));\r
48         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
49         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
50         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
51 }\r
52 \r
53 void FMSApp::displayHelp()\r
54 {\r
55         Poco::Util::HelpFormatter helpFormatter(options());\r
56         helpFormatter.setCommand(commandName());\r
57         helpFormatter.setUsage("OPTIONS");\r
58         helpFormatter.setHeader("The Freenet Message System.");\r
59         helpFormatter.format(std::cout);\r
60 }\r
61 \r
62 void FMSApp::handleHelp(const std::string &name, const std::string &value)\r
63 {\r
64         m_displayhelp=true;\r
65         displayHelp();\r
66         stopOptionsProcessing();\r
67 }\r
68 \r
69 void FMSApp::handleLogOption(const std::string &name, const std::string &value)\r
70 {\r
71         if(value=="file" || value=="stdout" || value=="stderr")\r
72         {\r
73                 m_logtype=value;\r
74         }\r
75 }\r
76 \r
77 void FMSApp::handleSetOption(const std::string &name, const std::string &value)\r
78 {\r
79         std::vector<std::string> valueparts;\r
80         StringFunctions::Split(value,"=",valueparts);\r
81 \r
82         if(valueparts.size()==2)\r
83         {\r
84                 m_setoptions[valueparts[0]]=valueparts[1];\r
85         }\r
86         else\r
87         {\r
88                 std::cout << "Expected option=value but found " << value << std::endl;\r
89         }\r
90 \r
91         m_setoption=true;\r
92 }\r
93 \r
94 void FMSApp::handleShowOptions(const std::string &name, const std::string &value)\r
95 {\r
96         m_showoptions=true;\r
97 }\r
98 \r
99 void FMSApp::initialize(Poco::Util::Application &self)\r
100 {\r
101         ServerApplication::initialize(self);\r
102 \r
103         // set working directory - fall back on application.dir if working directory isn't set\r
104         // if we are runing as a service, then working directory needs to be set to the application directory\r
105         if(m_workingdirectory=="" || config().getBool("application.runAsService",false)==true)\r
106         {\r
107                 m_workingdirectory=config().getString("application.dir");\r
108         }\r
109         int rval=chdir(m_workingdirectory.c_str());\r
110 \r
111 #ifdef QUERY_LOG\r
112         {\r
113                 Poco::AutoPtr<Poco::FormattingChannel> formatter=new Poco::FormattingChannel(new Poco::PatternFormatter("%Y-%m-%d %H:%M:%S | %t"));\r
114                 Poco::AutoPtr<Poco::FileChannel> fc=new Poco::FileChannel("query.log");\r
115                 fc->setProperty("rotation","daily");\r
116                 fc->setProperty("times","utc");\r
117                 fc->setProperty("archive","timestamp");\r
118                 fc->setProperty("purgeCount","5");\r
119                 fc->setProperty("compress","true");\r
120                 formatter->setChannel(fc);\r
121                 Poco::Logger::create("querylog",formatter,Poco::Message::PRIO_INFORMATION);\r
122         }\r
123 #endif\r
124 \r
125         LoadDatabase();\r
126         SetupDB(m_db);\r
127         SetupDefaultOptions(m_db);\r
128         initializeLogger();\r
129         config().setString("application.logger","logfile");\r
130 }\r
131 \r
132 void FMSApp::initializeLogger()\r
133 {\r
134         int initiallevel=Poco::Message::PRIO_TRACE;\r
135 \r
136         std::string tempval="";\r
137         Option option(m_db);\r
138 \r
139         if(option.Get("LogLevel",tempval))\r
140         {\r
141                 StringFunctions::Convert(tempval,initiallevel);\r
142         }\r
143 \r
144         Poco::AutoPtr<Poco::FormattingChannel> formatter=new Poco::FormattingChannel(new Poco::PatternFormatter("%Y-%m-%d %H:%M:%S | %p | %t"));\r
145         \r
146         if(m_logtype=="file")\r
147         {\r
148                 Poco::AutoPtr<Poco::FileChannel> fc=new Poco::FileChannel("fms.log");\r
149                 fc->setProperty("rotation","daily");    // rotate log file daily\r
150                 fc->setProperty("times","utc");                 // utc date/times for log entries\r
151                 fc->setProperty("archive","timestamp"); // add timestamp to old logs\r
152                 fc->setProperty("purgeCount","30");             // purge old logs after 30 logs have accumulated\r
153                 fc->setProperty("compress","true");             // gz compress old log files\r
154                 formatter->setChannel(fc);\r
155         }\r
156         else\r
157         {\r
158                 if(m_logtype=="stdout")\r
159                 {\r
160                         Poco::AutoPtr<Poco::ConsoleChannel> cc=new Poco::ConsoleChannel(std::cout);\r
161                         formatter->setChannel(cc);\r
162                 }\r
163                 else\r
164                 {\r
165                         Poco::AutoPtr<Poco::ConsoleChannel> cc=new Poco::ConsoleChannel(std::cerr);\r
166                         formatter->setChannel(cc);\r
167                 }\r
168         }\r
169         \r
170         setLogger(Poco::Logger::create("logfile",formatter,Poco::Message::PRIO_INFORMATION));\r
171         Poco::Logger::get("logfile").information("LogLevel set to "+tempval);\r
172         Poco::Logger::get("logfile").setLevel(initiallevel);\r
173 \r
174 }\r
175 \r
176 int FMSApp::main(const std::vector<std::string> &args)\r
177 {\r
178 \r
179         // running as a daemon would reset the working directory to / before calling main\r
180         // so we need to set the working directory again\r
181         int rval=chdir(m_workingdirectory.c_str());\r
182 \r
183         if(VerifyDB(m_db)==false)\r
184         {\r
185                 std::cout << "The FMS database failed verification.  It is most likely corrupt!" << std::endl;\r
186                 logger().fatal("The FMS database failed verification.  It is most likely corrupt!");\r
187         }\r
188         else if(m_displayhelp)\r
189         {\r
190         }\r
191         else if(m_showoptions)\r
192         {\r
193                 showOptions();\r
194         }\r
195         else if(m_setoption)\r
196         {\r
197                 setOptions();\r
198         }\r
199         else\r
200         {\r
201                 logger().information("FMS startup v"FMS_VERSION);\r
202                 logger().information("Using SQLite "SQLITE_VERSION);\r
203 \r
204                 std::string tempval("");\r
205                 Option option(m_db);\r
206                 option.Get("VacuumOnStartup",tempval);\r
207                 if(tempval=="true")\r
208                 {\r
209                         logger().information("VACUUMing database");\r
210                         m_db->Execute("VACUUM;");\r
211                 }\r
212 \r
213                 StartThreads();\r
214 \r
215                 if(isInteractive()==true)\r
216                 {\r
217                         std::cout << "FMS has been started." << std::endl << std::endl;\r
218                 }\r
219 \r
220                 waitForTerminationRequest();\r
221 \r
222                 if(isInteractive()==true)\r
223                 {\r
224                         std::cout << "Please wait while FMS shuts down." << std::endl << std::endl;\r
225                 }\r
226 \r
227                 logger().trace("FMSApp::main cancelling threads");\r
228                 m_threads.Cancel();\r
229                 logger().trace("FMSApp::main joining threads");\r
230                 m_threads.Join();\r
231 \r
232                 logger().information("FMS shutdown");\r
233         }\r
234 \r
235         return FMSApp::EXIT_OK;\r
236 }\r
237 \r
238 void FMSApp::setOptions()\r
239 {\r
240         for(std::map<std::string,std::string>::iterator i=m_setoptions.begin(); i!=m_setoptions.end(); i++)\r
241         {\r
242                 std::string tempval("");\r
243                 Option option(m_db);\r
244                 if(option.Get((*i).first,tempval))\r
245                 {\r
246                         option.Set((*i).first,(*i).second);\r
247                         std::cout << "Option " << (*i).first << " set to " << (*i).second << std::endl;\r
248                 }\r
249                 else\r
250                 {\r
251                         std::cout << "Unknown option " << (*i).first << std::endl;\r
252                 }\r
253         }\r
254 }\r
255 \r
256 void FMSApp::showOptions()\r
257 {\r
258         SQLite3DB::Statement st=m_db->Prepare("SELECT Option, OptionValue FROM tblOption;");\r
259         st.Step();\r
260         while(st.RowReturned())\r
261         {\r
262                 std::string option="";\r
263                 std::string optionvalue="";\r
264                 st.ResultText(0,option);\r
265                 st.ResultText(1,optionvalue);\r
266 \r
267                 std::cout << option << " = " << optionvalue << std::endl;\r
268 \r
269                 st.Step();\r
270         }\r
271 }\r
272 \r
273 void FMSApp::StartThreads()\r
274 {\r
275         std::string tempval("");\r
276         Option option(m_db);\r
277 \r
278         // always start the DB maintenance thread\r
279         logger().trace("FMSApp::StartThreads starting DBMaintenanceThread");\r
280         m_threads.Start(new DBMaintenanceThread());\r
281 \r
282         option.Get("StartHTTP",tempval);\r
283         if(tempval=="true")\r
284         {\r
285                 logger().trace("FMSApp::StartThreads starting HTTPThread");\r
286                 m_threads.Start(new HTTPThread());\r
287                 if(isInteractive())\r
288                 {\r
289                         std::cout << "Started HTTP Thread" << std::endl;\r
290                 }\r
291         }\r
292         else\r
293         {\r
294                 logger().trace("FMSApp::StartThreads not starting HTTPThread");\r
295         }\r
296 \r
297         tempval="";\r
298         option.Get("StartNNTP",tempval);\r
299         if(tempval=="true")\r
300         {\r
301                 logger().trace("FMSApp::StartThreads starting NNTPListener");\r
302                 m_threads.Start(new NNTPListener());\r
303                 if(isInteractive())\r
304                 {\r
305                         std::cout << "Started NNTP Thread" << std::endl;\r
306                 }\r
307         }\r
308         else\r
309         {\r
310                 logger().trace("FMSApp::StartThreads not starting NNTPListener");\r
311         }\r
312 \r
313         tempval="";\r
314         option.Get("StartFreenetUpdater",tempval);\r
315         if(tempval=="true")\r
316         {\r
317                 logger().trace("FMSApp::StartThreads starting FreenetMasterThread");\r
318                 m_threads.Start(new FreenetMasterThread());\r
319                 if(isInteractive())\r
320                 {\r
321                         std::cout << "Started Freenet FCP Thread" << std::endl;\r
322                 }\r
323         }\r
324         else\r
325         {\r
326                 logger().trace("FMSApp::StartThreads not starting FreenetMasterThread");\r
327         }\r
328 \r
329 }\r