973729955bbb4ebf0455f29eb65b72cd6aeb2c14
[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         SetupDB();\r
112         SetupDefaultOptions();\r
113         initializeLogger();\r
114         config().setString("application.logger","logfile");\r
115 }\r
116 \r
117 void FMSApp::initializeLogger()\r
118 {\r
119         int initiallevel=Poco::Message::PRIO_TRACE;\r
120 \r
121         std::string tempval="";\r
122         if(Option::Instance()->Get("LogLevel",tempval))\r
123         {\r
124                 StringFunctions::Convert(tempval,initiallevel);\r
125         }\r
126 \r
127         Poco::AutoPtr<Poco::FormattingChannel> formatter=new Poco::FormattingChannel(new Poco::PatternFormatter("%Y-%m-%d %H:%M:%S | %p | %t"));\r
128         \r
129         if(m_logtype=="file")\r
130         {\r
131                 Poco::AutoPtr<Poco::FileChannel> fc=new Poco::FileChannel("fms.log");\r
132                 fc->setProperty("rotation","daily");    // rotate log file daily\r
133                 fc->setProperty("times","utc");                 // utc date/times for log entries\r
134                 fc->setProperty("archive","timestamp"); // add timestamp to old logs\r
135                 fc->setProperty("purgeCount","30");             // purge old logs after 30 logs have accumulated\r
136                 fc->setProperty("compress","true");             // gz compress old log files\r
137                 formatter->setChannel(fc);\r
138         }\r
139         else\r
140         {\r
141                 if(m_logtype=="stdout")\r
142                 {\r
143                         Poco::AutoPtr<Poco::ConsoleChannel> cc=new Poco::ConsoleChannel(std::cout);\r
144                         formatter->setChannel(cc);\r
145                 }\r
146                 else\r
147                 {\r
148                         Poco::AutoPtr<Poco::ConsoleChannel> cc=new Poco::ConsoleChannel(std::cerr);\r
149                         formatter->setChannel(cc);\r
150                 }\r
151         }\r
152         \r
153         setLogger(Poco::Logger::create("logfile",formatter,Poco::Message::PRIO_INFORMATION));\r
154         Poco::Logger::get("logfile").information("LogLevel set to "+tempval);\r
155         Poco::Logger::get("logfile").setLevel(initiallevel);\r
156 }\r
157 \r
158 int FMSApp::main(const std::vector<std::string> &args)\r
159 {\r
160 \r
161         // running as a daemon would reset the working directory to / before calling main\r
162         // so we need to set the working directory again\r
163         int rval=chdir(m_workingdirectory.c_str());\r
164 \r
165         if(VerifyDB()==false)\r
166         {\r
167                 std::cout << "The FMS database failed verification.  It is most likely corrupt!" << std::endl;\r
168                 logger().fatal("The FMS database failed verification.  It is most likely corrupt!");\r
169         }\r
170         else if(m_displayhelp)\r
171         {\r
172         }\r
173         else if(m_showoptions)\r
174         {\r
175                 showOptions();\r
176         }\r
177         else if(m_setoption)\r
178         {\r
179                 setOptions();\r
180         }\r
181         else\r
182         {\r
183                 logger().information("FMS startup v"FMS_VERSION);\r
184 \r
185                 std::string tempval="";\r
186                 Option::Instance()->Get("VacuumOnStartup",tempval);\r
187                 if(tempval=="true")\r
188                 {\r
189                         logger().information("VACUUMing database");\r
190                         SQLite3DB::DB::Instance()->Execute("VACUUM;");\r
191                 }\r
192 \r
193                 StartThreads();\r
194 \r
195                 if(isInteractive()==true)\r
196                 {\r
197                         std::cout << "FMS has been started." << std::endl << std::endl;\r
198                 }\r
199 \r
200                 waitForTerminationRequest();\r
201 \r
202                 if(isInteractive()==true)\r
203                 {\r
204                         std::cout << "Please wait while FMS shuts down." << std::endl << std::endl;\r
205                 }\r
206 \r
207                 logger().trace("FMSApp::main cancelling threads");\r
208                 m_threads.Cancel();\r
209                 logger().trace("FMSApp::main joining threads");\r
210                 m_threads.Join();\r
211 \r
212                 logger().information("FMS shutdown");\r
213         }\r
214 \r
215         return FMSApp::EXIT_OK;\r
216 }\r
217 \r
218 void FMSApp::setOptions()\r
219 {\r
220         for(std::map<std::string,std::string>::iterator i=m_setoptions.begin(); i!=m_setoptions.end(); i++)\r
221         {\r
222                 std::string tempval="";\r
223                 if(Option::Instance()->Get((*i).first,tempval))\r
224                 {\r
225                         Option::Instance()->Set((*i).first,(*i).second);\r
226                         std::cout << "Option " << (*i).first << " set to " << (*i).second << std::endl;\r
227                 }\r
228                 else\r
229                 {\r
230                         std::cout << "Unknown option " << (*i).first << std::endl;\r
231                 }\r
232         }\r
233 }\r
234 \r
235 void FMSApp::showOptions()\r
236 {\r
237         SQLite3DB::Statement st=SQLite3DB::DB::Instance()->Prepare("SELECT Option, OptionValue FROM tblOption;");\r
238         st.Step();\r
239         while(st.RowReturned())\r
240         {\r
241                 std::string option="";\r
242                 std::string optionvalue="";\r
243                 st.ResultText(0,option);\r
244                 st.ResultText(1,optionvalue);\r
245 \r
246                 std::cout << option << " = " << optionvalue << std::endl;\r
247 \r
248                 st.Step();\r
249         }\r
250 }\r
251 \r
252 void FMSApp::StartThreads()\r
253 {\r
254         std::string tempval="";\r
255 \r
256         // always start the DB maintenance thread\r
257         logger().trace("FMSApp::StartThreads starting DBMaintenanceThread");\r
258         m_threads.Start(new DBMaintenanceThread());\r
259 \r
260         Option::Instance()->Get("StartHTTP",tempval);\r
261         if(tempval=="true")\r
262         {\r
263                 logger().trace("FMSApp::StartThreads starting HTTPThread");\r
264                 m_threads.Start(new HTTPThread());\r
265                 if(isInteractive())\r
266                 {\r
267                         std::cout << "Started HTTP Thread" << std::endl;\r
268                 }\r
269         }\r
270         else\r
271         {\r
272                 logger().trace("FMSApp::StartThreads not starting HTTPThread");\r
273         }\r
274 \r
275         tempval="";\r
276         Option::Instance()->Get("StartNNTP",tempval);\r
277         if(tempval=="true")\r
278         {\r
279                 logger().trace("FMSApp::StartThreads starting NNTPListener");\r
280                 m_threads.Start(new NNTPListener());\r
281                 if(isInteractive())\r
282                 {\r
283                         std::cout << "Started NNTP Thread" << std::endl;\r
284                 }\r
285         }\r
286         else\r
287         {\r
288                 logger().trace("FMSApp::StartThreads not starting NNTPListener");\r
289         }\r
290 \r
291         tempval="";\r
292         Option::Instance()->Get("StartFreenetUpdater",tempval);\r
293         if(tempval=="true")\r
294         {\r
295                 logger().trace("FMSApp::StartThreads starting FreenetMasterThread");\r
296                 m_threads.Start(new FreenetMasterThread());\r
297                 if(isInteractive())\r
298                 {\r
299                         std::cout << "Started Freenet FCP Thread" << std::endl;\r
300                 }\r
301         }\r
302         else\r
303         {\r
304                 logger().trace("FMSApp::StartThreads not starting FreenetMasterThread");\r
305         }\r
306 \r
307 }\r