sqlite: ...and clear "unread" flag on muted messages
[chiroptera.git] / receiver.d
blob5ea21e8a40688d42c9046b963dddae693980f4aa
1 /* E-Mail Client
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 // mail/nntp receiver thread
18 module receiver is aliced;
19 private:
21 import std.concurrency;
23 import iv.cmdcon;
24 import iv.strex;
25 import iv.sq3;
26 import iv.timer : DurTimer = Timer;
27 import iv.utfutil;
28 import iv.vfs.io;
30 import egui;
32 import chibackend;
33 import chibackend.net;
35 import chievents;
38 // ////////////////////////////////////////////////////////////////////////// //
39 __gshared bool rcDisabled = false;
40 __gshared bool rcStarted = false;
41 __gshared Tid controlThreadId;
44 enum ControlReply {
45 Quit,
48 struct ControlCommand {
49 enum Kind {
50 Ping,
51 ForceUpdateAll,
52 Quit,
54 CheckDone,
55 CheckError,
57 Kind type;
58 // for CheckDone or CheckError
59 uint accid;
61 @disable this ();
62 this (Kind atype) nothrow @safe @nogc { type = atype; accid = 0; }
63 this (Kind atype, uint aid) nothrow @safe @nogc { type = atype; accid = aid; }
67 struct CheckCommand {
68 uint accid;
72 static stmtAccInfo = LazyStatement!"Conf"(`
73 SELECT
74 accid AS accid
75 , checktime AS checktime
76 , nosendauth AS nosendauth
77 , debuglog AS debuglog
78 , nntplastindex AS nntplastindex
79 , name AS name
80 , recvserver AS recvserver
81 , sendserver AS sendserver
82 , user AS user
83 , pass AS pass
84 , inbox AS inbox
85 , nntpgroup AS nntpgroup
86 FROM accounts
87 WHERE accid=:accid
88 LIMIT 1
89 ;`);
92 static stmtSetCheckTime = LazyStatement!"Conf"(`
93 INSERT INTO checktimes(accid,lastcheck) VALUES(:accid,:lastcheck)
94 ON CONFLICT(accid)
95 DO UPDATE SET lastcheck=:lastcheck
96 ;`);
99 //==========================================================================
101 // checkerThread
103 //==========================================================================
104 void checkerThread (Tid ownerTid) {
105 uint accid = 0;
106 try {
107 receive(
108 (CheckCommand cmd) {
109 accid = cmd.accid;
113 if (accid == 0) {
114 ownerTid.send(ControlCommand(ControlCommand.Kind.CheckError, accid));
115 return;
118 bool found = false;
119 int checktime;
120 bool nosendauth;
121 bool debuglog;
122 uint nntplastindex;
123 DynStr name;
124 DynStr recvserver;
125 DynStr sendserver;
126 DynStr user;
127 DynStr pass;
128 DynStr inbox;
129 DynStr nntpgroup;
131 foreach (auto arow; stmtAccInfo.st.bind(":accid", accid).range) {
132 // i found her!
133 found = true;
134 int upmins = arow.checktime!int;
135 if (upmins < 1) upmins = 1; else if (upmins > 100000) upmins = 100000;
136 checktime = upmins;
137 nosendauth = (arow.nosendauth!int > 0);
138 debuglog = (arow.debuglog!int > 0);
139 nntplastindex = arow.nntplastindex!uint;
140 name = arow.name!SQ3Text;
141 recvserver = arow.recvserver!SQ3Text;
142 sendserver = arow.sendserver!SQ3Text;
143 user = arow.user!SQ3Text;
144 pass = arow.pass!SQ3Text;
145 inbox = arow.inbox!SQ3Text;
146 nntpgroup = arow.nntpgroup!SQ3Text;
149 if (!found) {
150 ownerTid.send(ControlCommand(ControlCommand.Kind.CheckError, accid));
151 return;
154 conwriteln("checking account '", name, "' (", accid, ")...");
156 stmtSetCheckTime.st.bind(":accid", accid).bind(":lastcheck", RunningAverageExp.GetTickCount()+checktime*60).doAll();
158 sqlite3_sleep(1000);
160 conwriteln("done checking account '", name, "' (", accid, ")...");
162 if (vbwin && !vbwin.closed) {
163 vbwin.postEvent(new UpdatingAccountCompleteEvent(accid));
164 sqlite3_sleep(1000);
166 } catch (Throwable e) {
167 // here, we are dead and fucked (the exact order doesn't matter)
168 //import core.stdc.stdlib : abort;
169 import core.stdc.stdio : fprintf, stderr;
170 //import core.memory : GC;
171 import core.thread : thread_suspendAll;
172 //GC.disable(); // yeah
173 //thread_suspendAll(); // stop right here, you criminal scum!
174 auto s = e.toString();
175 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
176 //abort(); // die, you bitch!
177 ownerTid.send(ControlCommand(ControlCommand.Kind.CheckError, accid));
178 return;
180 //if (vbwin) vbwin.postEvent(evDoConCommands); }
181 ownerTid.send(ControlCommand(ControlCommand.Kind.CheckDone, accid));
186 //==========================================================================
188 // controlThread
190 //==========================================================================
191 void controlThread (Tid ownerTid) {
192 import core.time;
193 bool doQuit = false;
194 try {
195 static struct AccCheck {
196 uint accid;
197 bool inprogress;
198 Tid tid;
201 AccCheck[] accidCheckList;
202 accidCheckList.reserve(128);
204 dbConf.execute(`
205 CREATE TEMP TABLE IF NOT EXISTS checktimes (
206 accid INTEGER PRIMARY KEY UNIQUE /* unique, never zero */
207 , lastcheck INTEGER NOT NULL DEFAULT 0
208 , checking INTEGER NOT NULL DEFAULT 0
212 static stmtAllAccs = LazyStatement!"Conf"(`
213 SELECT
214 accid AS accid
215 , checktime AS checktime
216 FROM accounts
217 WHERE nocheck=0 AND inbox<>''
218 ORDER BY accid
219 ;`);
221 static stmtGetCheckTime = LazyStatement!"Conf"(`
222 SELECT lastcheck AS lastcheck FROM checktimes WHERE accid=:accid LIMIT 1
223 ;`);
226 MonoTime lastCollect = MonoTime.currTime;
227 accidCheckList ~= AccCheck();
229 for (;;) {
230 if (doQuit && accidCheckList.length == 0) break;
231 bool forceAll = false;
232 receiveTimeout((doQuit ? 50.msecs : accidCheckList.length ? 1.seconds : 60.seconds),
233 (ControlCommand cmd) {
234 final switch (cmd.type) {
235 case ControlCommand.Kind.ForceUpdateAll: forceAll = true; break;
236 case ControlCommand.Kind.Ping: break;
237 case ControlCommand.Kind.Quit: doQuit = true; break;
238 case ControlCommand.Kind.CheckDone:
239 case ControlCommand.Kind.CheckError:
240 if (accidCheckList.length) {
241 foreach (immutable idx, const ref AccCheck nfo; accidCheckList) {
242 if (nfo.accid == cmd.accid) {
243 //if (!doQuit && vbwin && !vbwin.closed) vbwin.postEvent(new UpdatingAccountCompleteEvent(nfo.accid));
244 foreach (immutable c; idx+1..accidCheckList.length) accidCheckList[c-1] = accidCheckList[c];
245 accidCheckList.length -= 1;
246 break;
249 if (!doQuit && vbwin && !vbwin.closed && accidCheckList.length == 0) vbwin.postEvent(new UpdatingCompleteEvent());
251 break;
256 for (usize idx = 0; idx < accidCheckList.length; ) {
257 if (accidCheckList[idx].accid != 0) {
258 ++idx;
259 } else {
260 foreach (immutable c; idx+1..accidCheckList.length) accidCheckList[c-1] = accidCheckList[c];
261 accidCheckList.length -= 1;
265 if (doQuit) {
266 for (usize idx = 0; idx < accidCheckList.length; ) {
267 if (accidCheckList[idx].inprogress) {
268 ++idx;
269 } else {
270 foreach (immutable c; idx+1..accidCheckList.length) accidCheckList[c-1] = accidCheckList[c];
271 accidCheckList.length -= 1;
274 continue;
278 ulong ctt = RunningAverageExp.GetTickCount();
279 foreach (auto arow; stmtAllAccs.st.range) {
280 bool found = false;
281 foreach (const ref AccCheck nfo; accidCheckList) if (nfo.accid == arow.accid!uint) { found = true; break; }
282 if (found) continue;
283 // forced update?
284 if (forceAll) {
285 accidCheckList ~= AccCheck(arow.accid!uint);
286 continue;
288 // check timeout
289 int upmins = arow.checktime!int;
290 if (upmins < 1) upmins = 1; else if (upmins > 100000) upmins = 100000;
291 ulong lastcheck = 0;
292 foreach (auto crow; stmtGetCheckTime.st.bind(":accid", arow.accid!uint).range) lastcheck = crow.lastcheck!ulong;
293 lastcheck += upmins*60; // next check time
294 if (lastcheck < ctt) {
295 // i found her!
296 accidCheckList ~= AccCheck(arow.accid!uint);
299 else {
300 conwriteln("check for accid ", arow.accid!uint, " in ", (lastcheck-ctt)/60, " minutes...");
304 forceAll = false;
307 foreach (ref AccCheck nfo; accidCheckList) {
308 if (nfo.inprogress) break;
309 if (vbwin) vbwin.postEvent(new UpdatingAccountEvent(nfo.accid));
310 nfo.tid = spawn(&checkerThread, thisTid);
311 nfo.inprogress = true;
312 nfo.tid.send(CheckCommand(nfo.accid));
313 break;
316 if (!doQuit) {
317 immutable ctt = MonoTime.currTime;
318 if ((ctt-lastCollect).total!"minutes" >= 5) {
319 import core.memory : GC;
320 lastCollect = ctt;
321 GC.collect();
322 GC.minimize();
326 ownerTid.send(ControlReply.Quit);
327 } catch (Throwable e) {
328 // here, we are dead and fucked (the exact order doesn't matter)
329 import core.stdc.stdlib : abort;
330 import core.stdc.stdio : fprintf, stderr;
331 import core.memory : GC;
332 import core.thread : thread_suspendAll;
333 GC.disable(); // yeah
334 thread_suspendAll(); // stop right here, you criminal scum!
335 auto s = e.toString();
336 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
337 abort(); // die, you bitch!
342 //==========================================================================
344 // receiverDisable
346 //==========================================================================
347 public void receiverDisable () {
348 rcDisabled = true;
352 //==========================================================================
354 // receiverForceUpdateAll
356 //==========================================================================
357 public void receiverForceUpdateAll () {
358 if (!rcStarted) return;
359 controlThreadId.send(ControlCommand(ControlCommand.Kind.ForceUpdateAll));
363 //==========================================================================
365 // receiverInit
367 //==========================================================================
368 public void receiverInit () {
369 if (rcStarted) return;
370 if (rcDisabled) return;
371 controlThreadId = spawn(&controlThread, thisTid);
372 rcStarted = true;
376 //==========================================================================
378 // receiverDeinit
380 //==========================================================================
381 public void receiverDeinit () {
382 if (!rcStarted) return;
383 controlThreadId.send(ControlCommand(ControlCommand.Kind.Quit));
384 bool done = false;
385 while (!done) {
386 receive(
387 (ControlReply reply) {
388 if (reply == ControlReply.Quit) done = true;