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