0ecfeda26dc994d85bf753968c8b5155dc9c9fe3
[fms.git] / src / dbmaintenancethread.cpp
1 #include "../include/dbmaintenancethread.h"\r
2 #include "../include/stringfunctions.h"\r
3 #include "../include/option.h"\r
4 #include "../include/threadbuilder.h"\r
5 #include "../include/dbsetup.h"\r
6 \r
7 #include <Poco/Timestamp.h>\r
8 #include <Poco/Timespan.h>\r
9 #include <Poco/DateTimeFormatter.h>\r
10 #include <Poco/Thread.h>\r
11 \r
12 DBMaintenanceThread::DBMaintenanceThread()\r
13 {\r
14         // move last maintenance times back so they will all run soon\r
15         m_last10minute=Poco::Timestamp();\r
16         m_last30minute=Poco::Timestamp();\r
17         m_last30minute-=Poco::Timespan(0,0,11,0,0);\r
18         m_last1hour=Poco::Timestamp();\r
19         m_last1hour-=Poco::Timespan(0,0,49,0,0);\r
20         m_last6hour=Poco::Timestamp();\r
21         m_last6hour-=Poco::Timespan(0,5,42,0,0);\r
22         m_last1day=Poco::Timestamp();\r
23         m_last1day-=Poco::Timespan(0,23,51,0,0);\r
24 \r
25         m_deletemessagesolderthan=180;\r
26         std::string tempval="180";\r
27         Option::Instance()->Get("DeleteMessagesOlderThan",tempval);\r
28         StringFunctions::Convert(tempval,m_deletemessagesolderthan);\r
29 \r
30         m_messagedownloadmaxdaysbackward=5;\r
31         tempval="5";\r
32         Option::Instance()->Get("MessageDownloadMaxDaysBackward",tempval);\r
33         StringFunctions::Convert(tempval,m_messagedownloadmaxdaysbackward);\r
34 \r
35 }\r
36 \r
37 \r
38 void DBMaintenanceThread::Do10MinuteMaintenance()\r
39 {\r
40         std::string ll="";\r
41         Option::Instance()->Get("LogLevel",ll);\r
42 \r
43         // TODO - remove after corruption issue fixed\r
44         if(ll=="8")\r
45         {\r
46                 std::string dbres=TestDBIntegrity();\r
47                 m_log->trace("DBMaintenanceThread::Do10MinuteMaintenance() start TestDBIntegrity returned "+dbres);\r
48         }\r
49 \r
50         ThreadBuilder tb;\r
51         SQLite3DB::Statement boardst=m_db->Prepare("SELECT BoardID FROM tblBoard WHERE Forum='true';");\r
52         // select messages for a board that aren't in a thread\r
53         SQLite3DB::Statement selectst=m_db->Prepare("SELECT tblMessage.MessageID FROM tblMessage \\r
54                                                                                                 INNER JOIN tblMessageBoard ON tblMessage.MessageID=tblMessageBoard.MessageID\\r
55                                                                                                 LEFT JOIN tblThreadPost ON tblMessage.MessageID=tblThreadPost.MessageID \\r
56                                                                                                 LEFT JOIN tblThread ON tblThreadPost.ThreadID=tblThread.ThreadID\\r
57                                                                                                 WHERE tblMessageBoard.BoardID=? AND tblThread.BoardID IS NULL;");\r
58 \r
59         boardst.Step();\r
60         while(boardst.RowReturned())\r
61         {\r
62                 int boardid=-1;\r
63 \r
64                 boardst.ResultInt(0,boardid);\r
65 \r
66                 selectst.Bind(0,boardid);\r
67                 selectst.Step();\r
68 \r
69                 while(selectst.RowReturned())\r
70                 {\r
71                         int messageid=-1;\r
72 \r
73                         selectst.ResultInt(0,messageid);\r
74 \r
75                         tb.Build(messageid,boardid,true);\r
76 \r
77                         selectst.Step();\r
78                 }\r
79                 selectst.Reset();\r
80 \r
81                 boardst.Step();\r
82         }\r
83 \r
84         // TODO - remove after corruption issue fixed\r
85         if(ll=="8")\r
86         {\r
87                 std::string dbres=TestDBIntegrity();\r
88                 m_log->trace("DBMaintenanceThread::Do10MinuteMaintenance() middle TestDBIntegrity returned "+dbres);\r
89         }\r
90 \r
91         // now rebuild threads where the message has been deleted\r
92         SQLite3DB::Statement st=m_db->Prepare("SELECT tblThreadPost.MessageID, tblThread.BoardID FROM tblThreadPost INNER JOIN tblThread ON tblThreadPost.ThreadID=tblThread.ThreadID LEFT JOIN tblMessage ON tblThreadPost.MessageID=tblMessage.MessageID WHERE tblMessage.MessageID IS NULL;");\r
93         st.Step();\r
94         while(st.RowReturned())\r
95         {\r
96                 int messageid=-1;\r
97                 int boardid=-1;\r
98 \r
99                 st.ResultInt(0,messageid);\r
100                 st.ResultInt(1,boardid);\r
101 \r
102                 tb.Build(messageid,boardid,true);\r
103 \r
104                 st.Step();\r
105         }\r
106 \r
107         // TODO - remove after corruption issue fixed\r
108         if(ll=="8")\r
109         {\r
110                 std::string dbres=TestDBIntegrity();\r
111                 m_log->trace("DBMaintenanceThread::Do10MinuteMaintenance() end TestDBIntegrity returned "+dbres);\r
112         }\r
113 \r
114         m_log->debug("PeriodicDBMaintenance::Do10MinuteMaintenance");\r
115 }\r
116 \r
117 void DBMaintenanceThread::Do30MinuteMaintenance()\r
118 {\r
119         // UNCOMMENT method in run when code is placed here\r
120         m_log->debug("PeriodicDBMaintenance::Do30MinuteMaintenance");\r
121 }\r
122 \r
123 void DBMaintenanceThread::Do1HourMaintenance()\r
124 {\r
125         // recalculate all trust levels - this is CPU instensive\r
126         // do 1 identity at a time as doing it with 1 UPDATE statement locks that database for the duration\r
127         SQLite3DB::Statement st=m_db->Prepare("SELECT TargetIdentityID,PeerMessageTrust,PeerTrustListTrust FROM vwCalculatedPeerTrust;");\r
128         SQLite3DB::Statement upd=m_db->Prepare("UPDATE tblIdentity SET PeerMessageTrust=?, PeerTrustListTrust=? WHERE IdentityID=?");\r
129         st.Step();\r
130         while(st.RowReturned())\r
131         {\r
132                 int identityid=0;\r
133                 int trust=0;\r
134                 \r
135                 st.ResultInt(0,identityid);\r
136 \r
137                 upd.Bind(0,identityid);\r
138                 if(st.ResultNull(1)==false)\r
139                 {\r
140                         trust=0;\r
141                         st.ResultInt(1,trust);\r
142                         upd.Bind(0,trust);\r
143                 }\r
144                 else\r
145                 {\r
146                         upd.Bind(0);\r
147                 }\r
148                 if(st.ResultNull(2)==false)\r
149                 {\r
150                         trust=0;\r
151                         st.ResultInt(2,trust);\r
152                         upd.Bind(1,trust);\r
153                 }\r
154                 else\r
155                 {\r
156                         upd.Bind(1);\r
157                 }\r
158                 upd.Bind(2,identityid);\r
159                 upd.Step();\r
160                 upd.Reset();\r
161 \r
162                 st.Step();\r
163         }\r
164 \r
165         // set null peer trust for identities without a calculated trust\r
166         st=m_db->Prepare("SELECT IdentityID FROM tblIdentity WHERE IdentityID NOT IN (SELECT TargetIdentityID FROM vwCalculatedPeerTrust);");\r
167         upd=m_db->Prepare("UPDATE tblIdentity SET PeerMessageTrust=NULL, PeerTrustListTrust=NULL WHERE IdentityID=?;");\r
168         st.Step();\r
169         while(st.RowReturned())\r
170         {\r
171                 int identityid=0;\r
172                 st.ResultInt(0,identityid);\r
173                 upd.Bind(0,identityid);\r
174                 upd.Step();\r
175                 upd.Reset();\r
176                 st.Step();\r
177         }\r
178 \r
179         // insert all identities not in trust list already\r
180         m_db->Execute("INSERT INTO tblIdentityTrust(LocalIdentityID,IdentityID) SELECT LocalIdentityID,IdentityID FROM tblLocalIdentity,tblIdentity WHERE LocalIdentityID || '_' || IdentityID NOT IN (SELECT LocalIdentityID || '_' || IdentityID FROM tblIdentityTrust);");\r
181 \r
182         m_log->debug("PeriodicDBMaintenance::Do1HourMaintenance");\r
183 }\r
184 \r
185 void DBMaintenanceThread::Do6HourMaintenance()\r
186 {\r
187 \r
188         // if we remove a board and the reply boardid is still set to it, we need to replace it with a boardid that does exist\r
189         SQLite3DB::Statement st=m_db->Prepare("SELECT MessageID FROM tblMessage WHERE ReplyBoardID NOT IN (SELECT BoardID FROM tblBoard);");\r
190         SQLite3DB::Statement st2=m_db->Prepare("SELECT BoardID FROM tblMessageBoard WHERE MessageID=?;");\r
191         SQLite3DB::Statement upd=m_db->Prepare("UPDATE tblMessage SET ReplyBoardID=? WHERE MessageID=?;");\r
192         st.Step();\r
193         while(st.RowReturned())\r
194         {\r
195                 // find a valid boardid for the message\r
196                 int messageid=0;\r
197                 int boardid=0;\r
198 \r
199                 st.ResultInt(0,messageid);\r
200 \r
201                 st2.Bind(0,messageid);\r
202                 st2.Step();\r
203                 if(st2.RowReturned())\r
204                 {\r
205                         st2.ResultInt(0,boardid);\r
206                         upd.Bind(0,boardid);\r
207                         upd.Bind(1,messageid);\r
208                         upd.Step();\r
209                         upd.Reset();\r
210                 }\r
211                 st2.Reset();\r
212                 \r
213                 st.Step();\r
214         }\r
215 \r
216         m_log->debug("PeriodicDBMaintenance::Do6HourMaintenance");\r
217 }\r
218 \r
219 void DBMaintenanceThread::Do1DayMaintenance()\r
220 {\r
221         Poco::DateTime date;\r
222 \r
223         // delete all puzzles 2 or more days old\r
224         date=Poco::Timestamp();\r
225         date-=Poco::Timespan(2,0,0,0,0);\r
226         m_db->Execute("DELETE FROM tblIntroductionPuzzleInserts WHERE Day<='"+Poco::DateTimeFormatter::format(date,"%Y-%m-%d")+"';");\r
227         m_db->Execute("DELETE FROM tblIntroductionPuzzleRequests WHERE Day<='"+Poco::DateTimeFormatter::format(date,"%Y-%m-%d")+"';");\r
228 \r
229         // delete all identities we've never seen and were added more than 20 days ago\r
230         date=Poco::Timestamp();\r
231         date-=Poco::Timespan(20,0,0,0,0);\r
232         m_db->Execute("DELETE FROM tblIdentity WHERE LastSeen IS NULL AND DateAdded<'"+Poco::DateTimeFormatter::format(date,"%Y-%m-%d")+"';");\r
233 \r
234         // delete old identity requests - we don't need them anymore\r
235         date=Poco::Timestamp();\r
236         date-=Poco::Timespan(2,0,0,0,0);\r
237         m_db->Execute("DELETE FROM tblIdentityRequests WHERE Day<'"+Poco::DateTimeFormatter::format(date,"%Y-%m-%d")+"';");\r
238 \r
239         // delete old board list inserts/requests - we don't need them anymore\r
240         date=Poco::Timestamp();\r
241         date-=Poco::Timespan(2,0,0,0,0);\r
242         m_db->Execute("DELETE FROM tblBoardListInserts WHERE Day<'"+Poco::DateTimeFormatter::format(date,"%Y-%m-%d")+"';");\r
243         m_db->Execute("DELETE FROM tblBoardListRequests WHERE Day<'"+Poco::DateTimeFormatter::format(date,"%Y-%m-%d")+"';");\r
244 \r
245         // delete old local identity inserts - we don't need them anymore\r
246         date=Poco::Timestamp();\r
247         date-=Poco::Timespan(2,0,0,0,0);\r
248         m_db->Execute("DELETE FROM tblLocalIdentityInserts WHERE Day<'"+Poco::DateTimeFormatter::format(date,"%Y-%m-%d")+"';");\r
249 \r
250         // delete old message list inserts/requests - we don't need them anymore\r
251         date=Poco::Timestamp();\r
252         date-=Poco::Timespan(2,0,0,0,0);\r
253         m_db->Execute("DELETE FROM tblMessageListInserts WHERE Day<'"+Poco::DateTimeFormatter::format(date,"%Y-%m-%d")+"';");\r
254         m_db->Execute("DELETE FROM tblMessageListRequests WHERE Day<'"+Poco::DateTimeFormatter::format(date,"%Y-%m-%d")+"';");\r
255 \r
256         // delete old trust list inserts/requests - we don't need them anymore\r
257         date=Poco::Timestamp();\r
258         date-=Poco::Timespan(2,0,0,0,0);\r
259         m_db->Execute("DELETE FROM tblTrustListInserts WHERE Day<'"+Poco::DateTimeFormatter::format(date,"%Y-%m-%d")+"';");\r
260         m_db->Execute("DELETE FROM tblTrustListRequests WHERE Day<'"+Poco::DateTimeFormatter::format(date,"%Y-%m-%d")+"';");\r
261 \r
262         // delete trust lists from identities we aren't trusting anymore\r
263         m_db->Execute("DELETE FROM tblPeerTrust WHERE IdentityID NOT IN (SELECT IdentityID FROM tblIdentity WHERE (LocalTrustListTrust>=(SELECT OptionValue FROM tblOption WHERE Option='MinLocalTrustListTrust')) AND (PeerTrustListTrust IS NULL OR PeerTrustListTrust>=(SELECT OptionValue FROM tblOption WHERE Option='MinPeerTrustListTrust')));");\r
264 \r
265         // remove identityid from messages where the identity has been deleted\r
266         m_db->Execute("UPDATE tblMessage SET IdentityID=NULL WHERE IdentityID NOT IN (SELECT IdentityID FROM tblIdentity);");\r
267 \r
268         // try to re-attach messages from identities that were previously deleted, but have been since re-added\r
269         // first get the names from messages that have a NULL IdentityID\r
270         SQLite3DB::Statement st=m_db->Prepare("SELECT FromName FROM tblMessage WHERE IdentityID IS NULL GROUP BY FromName;");\r
271         st.Step();\r
272         while(st.RowReturned())\r
273         {\r
274                 std::string name="";\r
275                 std::string namepart="";\r
276                 std::string publickey="";\r
277                 int identityid=0;\r
278                 st.ResultText(0,name);\r
279 \r
280                 std::vector<std::string> parts;\r
281                 StringFunctions::Split(name,"@",parts);\r
282 \r
283                 // name can have a @ in it - so reattach all parts except the last which is the key\r
284                 for(long i=0; i<parts.size()-1; i++)\r
285                 {\r
286                         if(i!=0)\r
287                         {\r
288                                 namepart+="@";\r
289                         }\r
290                         namepart+=parts[i];\r
291                 }\r
292 \r
293                 // find identities with this name\r
294                 SQLite3DB::Statement st2=m_db->Prepare("SELECT IdentityID,PublicKey FROM tblIdentity WHERE Name=?;");\r
295                 st2.Bind(0,namepart);\r
296                 st2.Step();\r
297                 while(st2.RowReturned())\r
298                 {\r
299                         publickey="";\r
300                         identityid=0;\r
301                         st2.ResultText(1,publickey);\r
302                         // check if public key matches 2nd part\r
303                         if(parts.size()>1 && publickey.find(parts[1])==4)\r
304                         {\r
305                                 // we have the identity - so update the messages table with the identityid\r
306                                 st2.ResultInt(0,identityid);\r
307 \r
308                                 SQLite3DB::Statement st3=m_db->Prepare("UPDATE tblMessage SET IdentityID=? WHERE FromName=? AND IdentityID IS NULL;");\r
309                                 st3.Bind(0,identityid);\r
310                                 st3.Bind(1,name);\r
311                                 st3.Step();\r
312                         }\r
313                         st2.Step();\r
314                 }\r
315 \r
316                 st.Step();\r
317         }\r
318 \r
319         // delete single use identities that are older than 7 days\r
320         date=Poco::Timestamp();\r
321         date-=Poco::Timespan(7,0,0,0,0);\r
322         st=m_db->Prepare("DELETE FROM tblIdentity WHERE SingleUse='true' AND DateAdded<?;");\r
323         st.Bind(0,Poco::DateTimeFormatter::format(date,"%Y-%m-%d %H:%M:%S"));\r
324         st.Step();\r
325 \r
326         // delete local single use identities that are older than 7 days\r
327         date=Poco::Timestamp();\r
328         date-=Poco::Timespan(7,0,0,0,0);\r
329         st=m_db->Prepare("DELETE FROM tblLocalIdentity WHERE SingleUse='true' AND DateCreated<?;");\r
330         st.Bind(0,Poco::DateTimeFormatter::format(date,"%Y-%m-%d %H:%M:%S"));\r
331         st.Step();\r
332 \r
333         // delete old messages\r
334         date=Poco::Timestamp();\r
335         date-=Poco::Timespan(m_deletemessagesolderthan,0,0,0,0);\r
336         m_log->trace("PeriodicDBMaintenance::Do1DayMaintenance deleting messages prior to "+Poco::DateTimeFormatter::format(date,"%Y-%m-%d"));\r
337         st=m_db->Prepare("DELETE FROM tblMessage WHERE MessageDate<?;");\r
338         st.Bind(0,Poco::DateTimeFormatter::format(date,"%Y-%m-%d"));\r
339         st.Step();\r
340 \r
341         // delete old message requests\r
342         date=Poco::Timestamp();\r
343         date-=Poco::Timespan(m_messagedownloadmaxdaysbackward,0,0,0,0);\r
344         st=m_db->Prepare("DELETE FROM tblMessageRequests WHERE Day<?;");\r
345         st.Bind(0,Poco::DateTimeFormatter::format(date,"%Y-%m-%d"));\r
346         st.Step();\r
347 \r
348         // delete tblIdentityTrust for local identities and identities that have been deleted\r
349         m_db->Execute("DELETE FROM tblIdentityTrust WHERE LocalIdentityID NOT IN (SELECT LocalIdentityID FROM tblLocalIdentity);");\r
350         m_db->Execute("DELETE FROM tblIdentityTrust WHERE IdentityID NOT IN (SELECT IdentityID FROM tblIdentity);");\r
351 \r
352         m_log->debug("PeriodicDBMaintenance::Do1DayMaintenance");\r
353 \r
354 }\r
355 \r
356 void DBMaintenanceThread::run()\r
357 {\r
358         m_log->debug("DBMaintenanceThread::run thread started.");\r
359 \r
360         Poco::DateTime now;\r
361         int i=0;\r
362 \r
363         do\r
364         {\r
365                 now=Poco::Timestamp();\r
366 \r
367                 if((m_last10minute+Poco::Timespan(0,0,10,0,0))<=now)\r
368                 {\r
369                         Do10MinuteMaintenance();\r
370                         m_last10minute=Poco::Timestamp();\r
371                 }\r
372                 /*\r
373                 if((m_last30minute+Poco::Timespan(0,0,30,0,0))<=now)\r
374                 {\r
375                         Do30MinuteMaintenance();\r
376                         m_last30minute=Poco::Timestamp();\r
377                 }\r
378                 */\r
379                 if((m_last1hour+Poco::Timespan(0,1,0,0,0))<=now)\r
380                 {\r
381                         Do1HourMaintenance();\r
382                         m_last1hour=Poco::Timestamp();\r
383                 }\r
384                 if((m_last6hour+Poco::Timespan(0,6,0,0,0))<=now)\r
385                 {\r
386                         Do6HourMaintenance();\r
387                         m_last6hour=Poco::Timestamp();\r
388                 }\r
389                 if((m_last1day+Poco::Timespan(1,0,0,0,0))<=now)\r
390                 {\r
391                         Do1DayMaintenance();\r
392                         m_last1day=Poco::Timestamp();\r
393                 }\r
394 \r
395                 i=0;\r
396                 while(i++<5 && !IsCancelled())\r
397                 {\r
398                         Poco::Thread::sleep(1000);\r
399                 }\r
400 \r
401         }while(!IsCancelled());\r
402 \r
403         m_log->debug("DBMaintenanceThread::run thread exiting.");\r
404 }\r