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 module account
/*is aliced*/;
19 import arsd
.simpledisplay
;
35 // ////////////////////////////////////////////////////////////////////////// //
36 public class UpdatingAccountEvent
{ string accName
; this (string aaccName
) { accName
= aaccName
; } }
37 public class UpdatingAccountCompleteEvent
{ string accName
; this (string aaccName
) { accName
= aaccName
; } }
40 // ////////////////////////////////////////////////////////////////////////// //
41 abstract class Account
{
42 protected import core
.time
;
46 MonoTime lastUpdateTime
= MonoTime
.zero
;
53 string user
; // on server
59 int checkminutes
; // <=0: never
67 lastUpdateTime
= MonoTime
.currTime
;
70 Folder
getInboxFolder () nothrow @nogc {
71 foreach (Folder
fld; folders
) if (fld.folderPath
== inboxFolder
) return fld;
76 final @property string
sendQueueFileName () {
77 import std
.path
: buildPath
;
78 if (inboxFolder
.length
== 0) assert(0, "empty inbox folder, wtf?!");
79 return buildPath(mailRootDir
, inboxFolder
, "sendqueue.eml");
82 bool fixSendingArticleHeaders (Folder
fld, Article art
) {
83 art
.removeHeader("From");
84 if (realname
.length
) {
85 art
.prependHeader("From", encodeq(realname
)~" <"~mail
~">");
87 art
.prependHeader("From", mail
);
90 art
.replaceHeader("Message-ID", art
.msgid
);
91 string subj
= art
.subj
.xstrip
;
92 if (subj
.length
== 0) subj
= "no subject";
93 art
.replaceHeader("Subject", encodeq(subj
));
94 art
.replaceHeader("Mime-Version", "1.0");
95 art
.replaceHeader("Content-Type", "text/plain; charset=utf-8; format=flowed; delsp=no");
96 art
.replaceHeader("Content-Transfer-Encoding", "8bit");
97 art
.replaceHeader("User-Agent", "Chiroptera");
98 //if (art.inreplyto) art.replaceHeader("In-Reply-To", art.inreplyto);
99 if (art
.getHeaderValue("Date").length
== 0) art
.replaceHeader("Date", CurrTimeToRFCString
);
102 auto repto
= art
.getInReplyTo();
105 bool hadReplyTo
= false;
107 void addRef (const(char)[] s
) {
109 if (s
.length
== 0) return;
110 if (!hadReplyTo
&& s
== repto
) hadReplyTo
= true;
111 if (refs
.length
) refs
~= ' ';
115 void procRefs (const(char)[] s
) {
118 if (s
.length
== 0) break;
120 while (pos
< s
.length
&& s
[pos
] > ' ') ++pos
;
127 addRef(art
.getHeaderValue("References"));
128 if (!hadReplyTo
) addRef(repto
);
130 if (refs
.length
) art
.replaceHeader("References", refs
);
136 bool doPostArticle (Article art
);
139 final void markAsUpdatedNoLock () @nogc { lastUpdateTime
= MonoTime
.currTime
; mUpdateForced
= false; }
141 void doSendingNL () {
142 if (sendQueue
.length
== 0) return;
143 bool sqchanged
= false;
144 // send queued articles
146 while (idx
< sendQueue
.length
) {
147 //conwriteln(" POSTING #", idx+1, " of ", sendQueue.length, "...");
148 if (doPostArticle(sendQueue
[idx
])) {
150 foreach (immutable cc
; idx
+1..sendQueue
.length
) sendQueue
[cc
-1] = sendQueue
[cc
];
151 sendQueue
[$-1] = null; // so GC can collect it
152 sendQueue
.length
-= 1;
153 sendQueue
.assumeSafeAppend
;
159 if (sqchanged
) saveSendQueue();
160 //conwriteln("============");
164 final void forceUpdating () @nogc { synchronized(this) { lastUpdateTime
= MonoTime
.zero
; mUpdateForced
= true; } }
165 final void forceUpdated () @nogc { synchronized(this) { lastUpdateTime
= MonoTime
.currTime
; mUpdateForced
= false; } }
167 final bool needUpdate () @nogc {
169 if (mUpdating
) return false;
170 if (checkminutes
< 1 && !mUpdateForced
) return false;
171 if (mUpdateForced
) return true;
172 auto ctt
= MonoTime
.currTime
;
173 if ((ctt
-lastUpdateTime
).total
!"minutes" < checkminutes
) return false;
178 final @property bool updating () nothrow @nogc { bool res
; try { synchronized(this) res
= mUpdating
; } catch (Exception e
) {} return res
; /*fuck vanilla!*/ }
180 final void loadSendQueue () {
181 import std
.file
: exists
;
182 string fname
= sendQueueFileName
;
183 if (!fname
.exists
) return;
185 auto fl
= VFile(fname
);
187 auto art
= loadArticleText(fl
);
188 if (art
is null ||
!art
.valid
) break;
189 // hack: clear "loaded" flags
190 art
.markAsStandalone();
193 } catch (Exception e
) {
194 conwriteln("ERROR loading send queue for account '", name
, "'");
196 if (sendQueue
.length
) conwriteln("[", name
, "]: ", sendQueue
.length
, " message", (sendQueue
.length
> 1 ?
"s" : ""), " in send queue");
197 if (sendQueue
.length
) forceUpdating();
200 final void saveSendQueue () {
201 import std
.file
: exists
;
202 string fname
= sendQueueFileName
;
204 auto fl
= VFile(fname
, "w");
205 foreach (Article art
; sendQueue
) {
206 fl
.writeArticteText(art
);
207 art
.markAsStandalone();
209 } catch (Exception e
) {
210 conwriteln("ERROR writing send queue for account '", name
, "'");
215 // should be called ONLY from updater thread!
218 // this method should insert the article into `fld` (or another folder) if necessary
219 // next `update` should send articles (so the method may mark account for immediate update)
220 // art is "free" and can be modified freely
221 // WARNING! `fld` is NOT locked!
222 bool addToSendQueue (Folder
fld, Article art
) {
223 if (!art
.isStandalone
) { conwriteln("ERROR: can't add non-standalone article to send queue!"); return false; }
224 if (!art
.contentLoaded
) { conwriteln("ERROR: can't add article without content to send queue!"); return false; }
226 if (!fixSendingArticleHeaders(fld, art
)) return false;
227 art
.setupFromHeaders
!true();
229 art
.appendAttachments();
230 } catch (Exception e
) {
231 conwriteln("ERROR adding attachments: ", e
.msg
);
236 forceUpdating(); // so it will be sent
237 postDoCheckBoxesCycle(); // and do it now
243 final bool parseCommonOption (ref ConString
[] options
) {
244 if (options
.length
== 0) return false;
245 ConString opt
= options
[0];
246 if (opt
== "debuglog" || opt
== "debug_log") { this.debugdump
= true; options
= options
[1..$]; return true; }
247 if (opt
== "no_debuglog" || opt
== "no_debug_log") { this.debugdump
= false; options
= options
[1..$]; return true; }
248 if (opt
== "smtp_no_auth" || opt
== "smtp_noauth") { this.smtpNoAuth
= true; options
= options
[1..$]; return true; }
249 if (opt
== "default") { this.defaultFlag
= true; options
= options
[1..$]; return true; }
253 if (this.user
.length
) { conwriteln("ERROR: account '", this.name
, "': duplicate option '", opt
, "'"); throw new Exception("option error"); }
254 if (options
.length
< 2) { conwriteln("ERROR: account '", this.name
, "': no argument for option '", opt
, "'"); throw new Exception("option error"); }
256 if (arg
.length
== 0) { conwriteln("ERROR: account '", this.name
, "': empty option '", opt
, "'"); throw new Exception("option error"); }
257 options
= options
[2..$];
258 this.user
= arg
.idup
;
262 if (this.pass
.length
) { conwriteln("ERROR: account '", this.name
, "': duplicate option '", opt
, "'"); throw new Exception("option error"); }
263 if (options
.length
< 2) { conwriteln("ERROR: account '", this.name
, "': no argument for option '", opt
, "'"); throw new Exception("option error"); }
265 if (arg
.length
== 0) { conwriteln("ERROR: account '", this.name
, "': empty option '", opt
, "'"); throw new Exception("option error"); }
266 options
= options
[2..$];
267 this.pass
= arg
.idup
;
272 if (this.realname
.length
) { conwriteln("ERROR: account '", this.name
, "': duplicate option '", opt
, "'"); throw new Exception("option error"); }
273 if (options
.length
< 2) { conwriteln("ERROR: account '", this.name
, "': no argument for option '", opt
, "'"); throw new Exception("option error"); }
275 if (arg
.length
== 0) { conwriteln("ERROR: account '", this.name
, "': empty option '", opt
, "'"); throw new Exception("option error"); }
276 options
= options
[2..$];
277 this.realname
= arg
.idup
;
280 if (this.server
.length
) { conwriteln("ERROR: account '", this.name
, "': duplicate option '", opt
, "'"); throw new Exception("option error"); }
281 if (options
.length
< 2) { conwriteln("ERROR: account '", this.name
, "': no argument for option '", opt
, "'"); throw new Exception("option error"); }
283 if (arg
.length
== 0) { conwriteln("ERROR: account '", this.name
, "': empty option '", opt
, "'"); throw new Exception("option error"); }
284 options
= options
[2..$];
285 this.server
= arg
.idup
;
289 if (this.sendserver
.length
) { conwriteln("ERROR: account '", this.name
, "': duplicate option '", opt
, "'"); throw new Exception("option error"); }
290 if (options
.length
< 2) { conwriteln("ERROR: account '", this.name
, "': no argument for option '", opt
, "'"); throw new Exception("option error"); }
292 if (arg
.length
== 0) { conwriteln("ERROR: account '", this.name
, "': empty option '", opt
, "'"); throw new Exception("option error"); }
293 options
= options
[2..$];
294 this.sendserver
= arg
.idup
;
299 if (this.mail
.length
) { conwriteln("ERROR: account '", this.name
, "': duplicate option '", opt
, "'"); throw new Exception("option error"); }
300 if (options
.length
< 2) { conwriteln("ERROR: account '", this.name
, "': no argument for option '", opt
, "'"); throw new Exception("option error"); }
302 if (arg
.length
== 0) { conwriteln("ERROR: account '", this.name
, "': empty option '", opt
, "'"); throw new Exception("option error"); }
303 options
= options
[2..$];
304 this.mail
= arg
.idup
;
308 if (options
.length
< 2) { conwriteln("ERROR: account '", this.name
, "': no argument for option '", opt
, "'"); throw new Exception("option error"); }
310 if (arg
.length
== 0) { conwriteln("ERROR: account '", this.name
, "': empty option '", opt
, "'"); throw new Exception("option error"); }
311 options
= options
[2..$];
312 import std
.conv
: to
;
313 auto ctm
= arg
.to
!int;
314 if (ctm
< 0) ctm
= 0;
315 this.checkminutes
= ctm
;
323 void parseOptions (ConString
[] options
);
324 void checkParsedOptions ();
326 void accountBeforeAdd () {
327 import std
.file
: mkdirRecurse
;
328 import std
.path
: buildPath
;
329 if (inboxFolder
.length
== 0 || inboxFolder
[0] == '/' || inboxFolder
[$-1] == '/') { conwriteln("ERROR: account '", name
, "': invalid inbox folder: '", inboxFolder
, "'"); throw new Exception("option error"); }
330 mkdirRecurse(buildPath(mailRootDir
, inboxFolder
));
331 if (sendserver
.length
== 0) sendserver
= server
;
337 // ////////////////////////////////////////////////////////////////////////// //
338 public final class Pop3Account
: Account
{
340 private this (ConString aname
) {
341 inboxFolder
= "accounts/";
342 immutable xlen
= inboxFolder
.length
;
343 foreach (char ch
; aname
) {
344 if (ch
== '/' || ch
== '\\') {
345 if (inboxFolder
.length
> 0 && inboxFolder
[$-1] != '/') inboxFolder
~= '/';
350 if (inboxFolder
.length
<= xlen || inboxFolder
[$-1] == '/') throw new Exception("invalid inbox folder");
351 name
= inboxFolder
[xlen
..$];
352 inboxFolder
~= "/inbox";
357 // this method should insert the article into `fld` (or another folder) if necessary
358 // next `update` should send articles (so the method may mark account for immediate update)
359 // art is "free" and can be modified freely
360 // WARNING! `fld` is NOT locked!
361 override bool addToSendQueue (Folder
fld, Article art
) {
362 if (super.addToSendQueue(fld, art
)) {
364 auto newart
= art
.clone();
365 newart
.setupFromHeaders
!true();
366 fld.withBaseWriter((abase
) {
367 if (abase
.insert(newart
)) abase
.writeUpdates();
369 fld.markForRebuild();
370 fld.buildVisibleList();
378 // should be called ONLY from updater thread!
379 override void update () {
383 markAsUpdatedNoLock();
392 Folder inbox
= getInboxFolder
;
394 conwriteln("*** ERROR: [", name
, "]: no inbox!");
399 conwriteln("*** [", name
, "]: connecting...");
400 auto pop3
= new SocketPOP3(server
, debugdump
);
401 scope(exit
) pop3
.close();
402 conwriteln("[", name
, "]: authenticating...");
403 pop3
.auth(user
, pass
);
404 auto newmsg
= pop3
.getNewMailCount
;
406 conwriteln("[", name
, "]: no new messages");
409 conwriteln("[", name
, "]: ", newmsg
, " new message", (newmsg
> 1 ?
"s" : ""));
410 foreach (immutable int popidx
; 1..newmsg
+1) {
413 auto msg
= pop3
.getMessage(popidx
);
415 conwriteln("===============================");
416 foreach (string s; msg) conwriteln(s, "|");
417 conwriteln("-------------------------------");
419 auto art
= parseMessage(msg
);
420 pop3
.deleteMessage(popidx
);
423 conwriteln("From: ", art
.fromname
, " <", art
.frommail
, ">");
424 conwriteln("Subj: ", art
.subj
.recodeToKOI8
);
425 conwriteln("Date: ", SysTime
.fromUnixTime(art
.time
));
426 conwriteln("MID : ", art
.msgid
);
427 conwriteln("RID : ", art
.getInReplyTo());
429 auto eml = new IncomingEmailMessage(art.lines);
430 conwriteln("-------------------------------");
431 conwriteln("From: ", eml.from);
432 conwriteln("Subj: ", eml.subject.recodeToKOI8);
433 conwriteln(eml.textMessageBody.recodeToKOI8);
441 fda
.destfolder
= inboxFolder
;
443 doFiltering
!"pre"(fda
);
444 if (fda
.destfolder
.length
== 0) {
446 conwriteln("*DROP: ", art
.fromname
.recodeToKOI8
, " <", art
.frommail
, "> (", art
.subj
.recodeToKOI8
, ")");
450 Bogo bogo
= fda
.bogo
;
452 if (bogo
== Bogo
.Error
) bogo
= articleBogoCheck(art
);
454 final switch (bogo
) {
455 case Bogo
.Error
: break;
460 art
.spamUnsure
= true;
467 if (fda
.markRead
) art
.unread
= false;
470 doFiltering
!"post"(fda
);
472 fda
.destfolder
= "zz_spam";
475 if (fda
.destfolder
.length
== 0) {
477 conwriteln("*DROP: ", art
.fromname
.recodeToKOI8
, " <", art
.frommail
, "> (", art
.subj
.recodeToKOI8
, ")");
481 while (fda
.destfolder
.length
&& fda
.destfolder
[0] == '/') fda
.destfolder
= fda
.destfolder
[1..$];
482 while (fda
.destfolder
.length
&& fda
.destfolder
[$-1] == '/') fda
.destfolder
= fda
.destfolder
[0..$-1];
483 if (fda
.destfolder
.length
== 0) fda
.destfolder
= inboxFolder
;
485 if (fda
.destfolder
!= inboxFolder
) {
486 import std
.file
: mkdirRecurse
;
487 import std
.path
: buildPath
;
488 conwriteln("*** MOVE TO: [", fda
.destfolder
, "]");
489 // find folder to move
491 foreach (Folder
fld; folders
) if (fld.folderPath
== fda
.destfolder
) { xfolder
= fld; break; }
492 if (xfolder
is null) {
493 conwriteln(" *** ERROR: [", name
, "]: folder [", fda
.destfolder
, "] not found!");
494 } else if (xfolder
!is inbox
) {
496 xfolder
.withBaseWriter((abase
) {
497 auto aidx
= abase
.insert(art
);
498 if (aidx
&& fda
.softDelete
) abase
.softDeleted(aidx
, true);
499 if (xfolder
.isTwittedByArtIndexNL(aidx
) !is null) abase
.setRead(aidx
);
501 abase
.writeUpdates();
502 conwriteln(" ", abase
.length
-1, " messages in database (", abase
.aliveCount
, " alive)");
504 art
.releaseContent();
505 xfolder
.markForRebuild();
507 } catch (Exception e
) {
508 conwriteln("MOVE ERROR: ", e
.msg
);
513 inbox
.withBaseWriter((abase
) {
514 auto aidx
= abase
.insert(art
);
515 if (aidx
&& fda
.softDelete
) abase
.softDeleted(aidx
, true);
516 if (inbox
.isTwittedByArtIndexNL(aidx
) !is null) abase
.setRead(aidx
);
517 abase
.writeUpdates();
519 art
.releaseContent();
520 inbox
.markForRebuild();
523 conwriteln("[", name
, "]: ", inbox
.length
, " messages in database (", inbox
.aliveCount
, " alive)");
525 inbox.withBaseWriter((abase) {
527 abase.writeUpdates();
533 static const(char)[] extractToMail (Article art
) {
534 if (art
is null ||
!art
.valid
) return null;
535 //conwriteln("x0: ", art.headers);
536 auto dest
= art
.getHeaderValue("To");
537 //conwriteln("00: [", dest, "]");
538 auto atpos
= dest
.lastIndexOf('@');
539 if (atpos
<= 0 || dest
.length
-atpos
< 3) return null;
541 while (spos
> 0 && dest
[spos
-1] != '<') --spos
;
542 if (spos
== atpos
) return null;
544 while (epos
< dest
.length
&& dest
[epos
] != '>') ++epos
;
545 if (epos
< dest
.length
&& dest
.length
-epos
!= 1) return null;
546 return dest
[spos
..epos
];
549 // WARNING! `fld` is NOT locked!
550 override bool fixSendingArticleHeaders (Folder
fld, Article art
) {
551 if (!super.fixSendingArticleHeaders(fld, art
)) return false;
552 return (extractToMail(art
).length
!= 0);
555 override bool doPostArticle (Article art
) {
556 assert(art
!is null);
558 string from
= art
.frommail
;
559 if (from
.length
== 0) {
560 conwriteln("SMTP ERROR: no FROM!");
564 auto to
= extractToMail(art
);
565 if (to
.length
== 0) {
566 conwriteln("SMTP ERROR: no TO!");
570 conwriteln("[", name
, "]: trying to send mail from <", from
, "> to <", to
, "> using ", sendserver
);
574 nsk
= new SocketSMTP(sendserver
, /*debugdump*/true);
575 } catch (Exception e
) {
576 conwriteln("[", name
, "]: connection error: ", e
.msg
);
579 scope(exit
) nsk
.close();
582 if (!smtpNoAuth
) nsk
.auth(mail
, user
, pass
);
583 nsk
.sendMessage(from
, to
, art
.getTextToSend
);
584 } catch (Exception e
) {
585 conwriteln("[", name
, "]: sending error: ", e
.msg
);
593 override void parseOptions (ConString
[] options
) {
594 while (options
.length
) {
595 if (parseCommonOption(options
)) continue;
596 conwriteln("ERROR: account '", name
, "': unknown option '", options
[0], "'");
597 throw new Exception("option error");
601 override void checkParsedOptions () {
602 if (inboxFolder
.length
== 0 || inboxFolder
[0] == '/' || inboxFolder
[$-1] == '/') { conwriteln("ERROR: account '", name
, "': invalid inbox folder: '", inboxFolder
, "'"); throw new Exception("option error"); }
603 if (user
.length
== 0) { conwriteln("ERROR: account '", name
, "': no user"); throw new Exception("option error"); }
604 if (server
.length
== 0) { conwriteln("ERROR: account '", name
, "': no server"); throw new Exception("option error"); }
605 if (mail
.length
== 0) { conwriteln("ERROR: account '", name
, "': no mail"); throw new Exception("option error"); }
608 override void accountBeforeAdd () { super.accountBeforeAdd(); }
612 // ////////////////////////////////////////////////////////////////////////// //
613 public final class NntpAccount
: Account
{
615 string group
; // DO NOT MODIFY!
618 private this (ConString aname
) {
619 if (aname
.length
== 0) throw new Exception("empty account name");
625 // should be called ONLY from updater thread!
626 override void update () {
630 markAsUpdatedNoLock();
639 Folder inbox
= getInboxFolder
;
641 conwriteln("*** ERROR: [", name
, ":", group
, "]: no inbox!");
646 conwriteln("*** [", name
, ":", group
, "]: connecting...");
647 auto nsk
= new SocketNNTP(server
, debugdump
);
648 scope(exit
) nsk
.close();
650 nsk
.selectGroup(group
);
651 if (nsk
.emptyGroup
) {
652 conwriteln("[", name
, ":", group
, "]: no new articles");
655 if (debugdump
) conwriteln("[", name
, ":", group
, "]: lo=", nsk
.lowater
, "; hi=", nsk
.hiwater
, "; lastloaded=", inbox
.maxNntpIndex
);
657 uint stnum
= inbox
.maxNntpIndex
+1;
658 if (stnum
== 0) stnum
= (nsk
.hiwater
> 1023 ? nsk
.hiwater
-1023 : 0);
659 if (stnum
> nsk
.hiwater
) {
660 conwriteln("[", name
, ":", group
, "]: no new articles");
664 conwriteln("[", name
, ":", group
, "]: ", nsk
.hiwater
+1-stnum
, " (possible) new articles");
666 // download new articles
667 //bool wantNewLine = false;
668 foreach (immutable uint anum
; stnum
..nsk
.hiwater
+1) {
671 auto msg
= nsk
.getArticle(anum
);
675 art
= parseMessage(msg
);
676 } catch (Exception e
) {
677 conwriteln("=== ERROR: CANNOT PARSE MESSAGE ===\n", e
.msg
, "\n---\n", msg
, "\n---\n", e
.toString
);
681 if (debugdump ||
true) {
682 conwriteln("From: ", art
.fromname
, " <", art
.frommail
, ">");
683 conwriteln("Subj: ", art
.subj
.recodeToKOI8
);
684 conwriteln("Date: ", SysTime
.fromUnixTime(art
.time
));
685 conwriteln("MID : ", art
.msgid
);
686 conwriteln("RID : ", art
.getInReplyTo());
688 if (art
.nntpindex
== 0) throw new Exception("NNTP article without number!");
692 //TODO: separate filtering system
694 inbox
.withBaseWriter((abase
) {
695 auto aidx
= abase
.insert(art
);
696 auto twt
= inbox
.isTwittedByArtIndexNL(aidx
);
697 conwriteln("DB INDEX: ", aidx
, " (", abase
.length
, ")");
700 conwriteln(" ***FILTERED BY TWIT FILTER (", twt
.recodeToKOI8
, ")");
703 abase
.writeUpdates();
705 art
.releaseContent();
706 inbox
.markForRebuild();
709 conwriteln("[", name
, ":", group
, "]: ", inbox
.length
, " articles in database (", inbox
.aliveCount
, " alive)");
711 inbox.withBaseWriter((abase) {
713 abase.writeUpdates();
719 // WARNING! `fld` is NOT locked!
720 override bool fixSendingArticleHeaders (Folder
fld, Article art
) {
721 if (!super.fixSendingArticleHeaders(fld, art
)) return false;
723 art
.replaceHeader("Newsgroups", group
);
724 art
.removeHeader("To");
728 override bool doPostArticle (Article art
) {
729 assert(art
!is null);
733 nsk
= new SocketNNTP(server
, debugdump
);
734 } catch (Exception e
) {
735 conwriteln("[", name
, ":", group
, "]: connection error: ", e
.msg
);
738 scope(exit
) nsk
.close();
741 nsk
.selectGroup(group
);
743 nsk
.doSendRaw(art
.getTextToSend
);
744 auto ln
= nsk
.readLine
;
745 conwriteln(ln
); // 340 Ok, recommended message-ID <o7dq4o$mpm$1@digitalmars.com>
746 if (ln
.length
== 0 || ln
[0] != '3') throw new Exception(ln
.idup
);
747 } catch (Exception e
) {
748 conwriteln("[", name
, ":", group
, "]: sending error: ", e
.msg
);
756 override void parseOptions (ConString
[] options
) {
757 while (options
.length
) {
758 if (parseCommonOption(options
)) continue;
759 ConString opt
= options
[0];
763 if (group
.length
) { conwriteln("ERROR: account '", name
, "': duplicate option '", opt
, "'"); throw new Exception("option error"); }
764 if (options
.length
< 2) { conwriteln("ERROR: account '", name
, "': no argument for option '", opt
, "'"); throw new Exception("option error"); }
766 if (arg
.length
== 0) { conwriteln("ERROR: account '", name
, "': empty option '", opt
, "'"); throw new Exception("option error"); }
767 options
= options
[2..$];
768 foreach (char ch
; arg
) {
769 if (ch
!= '.' && !ch
.isalnum
) { conwriteln("ERROR: account '", name
, "': invalid group name: '", arg
, "'"); throw new Exception("option error"); }
774 if (inboxFolder
.length
) { conwriteln("ERROR: account '", name
, "': duplicate option '", opt
, "'"); throw new Exception("option error"); }
775 if (options
.length
< 2) { conwriteln("ERROR: account '", name
, "': no argument for option '", opt
, "'"); throw new Exception("option error"); }
777 if (arg
.length
== 0) { conwriteln("ERROR: account '", name
, "': empty option '", opt
, "'"); throw new Exception("option error"); }
778 options
= options
[2..$];
779 if (arg
[0] == '/' || arg
[$-1] == '/') { conwriteln("ERROR: account '", name
, "': invalid folder name: '", arg
, "'"); throw new Exception("option error"); }
780 inboxFolder
= arg
.idup
;
783 conwriteln("ERROR: account '", name
, "': unknown option '", options
[0], "'");
784 throw new Exception("option error");
789 override void checkParsedOptions () {
790 if (inboxFolder
.length
== 0 || inboxFolder
[0] == '/' || inboxFolder
[$-1] == '/') { conwriteln("ERROR: account '", name
, "': invalid inbox folder: '", inboxFolder
, "'"); throw new Exception("option error"); }
791 if (group
.length
== 0) { conwriteln("ERROR: account '", name
, "': no group"); throw new Exception("option error"); }
792 foreach (char ch
; group
) {
793 if (ch
!= '.' && !ch
.isalnum
) { conwriteln("ERROR: account '", name
, "': invalid group name: '", group
, "'"); throw new Exception("option error"); }
795 if (server
.length
== 0) { conwriteln("ERROR: account '", name
, "': no server"); throw new Exception("option error"); }
796 if (mail
.length
== 0) { conwriteln("ERROR: account '", name
, "': no mail"); throw new Exception("option error"); }
799 override void accountBeforeAdd () {
800 super.accountBeforeAdd();
801 if (auto inbox
= getInboxFolder
) inbox
.hideOldThreads
= true;
806 // ////////////////////////////////////////////////////////////////////////// //
807 __gshared Account
[] accounts
;
808 __gshared Account defaultAcc
;
811 // ////////////////////////////////////////////////////////////////////////// //
812 Account
findNntpAccountForFolder (Folder
fld) nothrow @nogc {
813 if (fld is null) return null;
814 foreach (Account acc
; accounts
) {
815 if (auto nac
= cast(NntpAccount
)acc
) {
816 if (fld is nac
.getInboxFolder
) return acc
;
823 // ////////////////////////////////////////////////////////////////////////// //
824 Account
accFindByMail (const(char)[] mail
) nothrow @nogc {
826 if (mail
.length
== 0) return null;
827 foreach (Account acc
; accounts
) {
828 if (acc
.mail
.length
== 0) continue;
829 if (acc
.mail
.strEquCI(mail
)) return acc
;
835 // ////////////////////////////////////////////////////////////////////////// //
836 Account
accFindByFolder (Folder
fld) nothrow @nogc {
837 if (fld is null) return null;
838 foreach (Account acc
; accounts
) {
839 if (acc
.inboxFolder
.strEquCI(fld.folderPath
)) return acc
;
845 // ////////////////////////////////////////////////////////////////////////// //
846 Account
accFindNntpByFolder (Folder
fld) nothrow @nogc {
847 if (fld is null) return null;
848 foreach (Account acc
; accounts
) {
849 if (auto nna
= cast(NntpAccount
)acc
) {
850 if (nna
.inboxFolder
.strEquCI(fld.folderPath
)) return acc
;
857 // ////////////////////////////////////////////////////////////////////////// //
858 shared static this () {
859 //popbox name options...
863 // server [tls:]server
864 // smtpserver [tls:]server
868 conRegFunc
!((ConString name
, ConString
[] options
) {
870 if (name
.length
== 0) { conwriteln("ERROR: empty account name!"); return; }
871 foreach (char ch
; name
) {
872 if (!ch
.isalnum
&& ch
!= '.' && ch
!= '_' && ch
!= '-') { conwriteln("ERROR: invalid account name: '", name
, "'"); return; }
874 auto acc
= new Pop3Account(name
);
875 acc
.parseOptions(options
);
876 acc
.checkParsedOptions();
877 synchronized(Account
.classinfo
) {
878 foreach (ref Account a
; accounts
) if (a
.name
== acc
.name
) { conwriteln("ERROR: duplicate account name: '", name
, "'"); return; }
879 acc
.accountBeforeAdd();
880 conwriteln("POP3 server '", acc
.name
, "': ", acc
.server
, " (", acc
.mail
, ")");
882 if (acc
.defaultFlag
) defaultAcc
= acc
;
884 } catch (Exception e
) {
885 conwriteln("ERROR: ", e
.msg
);
886 //conwriteln(e.toString);
888 })("popbox", "register POP3 mail box");
890 //nntpbox name options...
896 // folder inbox_folder (required!)
897 // group nntp_group_name (required!)
900 conRegFunc
!((ConString name
, ConString
[] options
) {
902 if (name
.length
== 0) { conwriteln("ERROR: empty account name!"); return; }
903 foreach (char ch
; name
) {
904 if (!ch
.isalnum
&& ch
!= '.' && ch
!= '_' && ch
!= '-') { conwriteln("ERROR: invalid account name: '", name
, "'"); return; }
906 auto acc
= new NntpAccount(name
);
907 acc
.parseOptions(options
);
908 acc
.checkParsedOptions();
909 synchronized(Account
.classinfo
) {
910 foreach (ref Account a
; accounts
) if (a
.name
== acc
.name
) { conwriteln("ERROR: duplicate account name: '", name
, "'"); return; }
911 acc
.accountBeforeAdd();
912 conwriteln("NNTP server: ", acc
.server
, " (", acc
.group
, ")");
915 } catch (Exception e
) {
916 conwriteln("ERROR: ", e
.msg
);
917 //conwriteln(e.toString);
919 })("nntpbox", "register NNTP box");