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