1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
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 accdb
is aliced
;
31 // ////////////////////////////////////////////////////////////////////////// //
32 public alias TextDigest
= ubyte[20]; // ripemd160
34 public TextDigest
textDigest (const(void)[] buf
) nothrow @trusted @nogc {
35 import std
.digest
.ripemd
;
36 return ripemd160Of(buf
);
40 public SysTime
systimeNow () nothrow @trusted {
42 SysTime st
= Clock
.currTime
;
43 return SysTime
.fromUnixTime(st
.toUnixTime
); // trunc to second precision
44 } catch (Exception e
) {}
45 assert(0, "time error!");
49 // ////////////////////////////////////////////////////////////////////////// //
50 enum TriOption
{ Default
= -1, No
= 0, Yes
= 1 }
52 enum ContactStatus
{ Offline
, Online
, Away
, Busy
, Connecting
}
54 alias PubKey
= ubyte[TOX_PUBLIC_KEY_SIZE
];
55 alias ToxAddr
= ubyte[TOX_ADDRESS_SIZE
];
58 // ////////////////////////////////////////////////////////////////////////// //
59 struct CommonOptions
{
60 TriOption showOffline
= TriOption
.Default
; // show this contact even if it is offline
61 TriOption showPopup
= TriOption
.Default
; // show popups for messages from this contact
62 TriOption blinkActivity
= TriOption
.Default
; // blink tray icon if we have some activity for this contact
63 TriOption skipUnread
= TriOption
.Default
; // skip this contacts in `next_unread` command
64 TriOption ftranAllowed
= TriOption
.Default
; // file transfers allowed for this contact
65 int resendRotDays
= -1; // how many days message should be in "resend queue" if contact is offline (-1: use default value)
66 int hmcOnOpen
= -1; // how many history messages we should show when we opening a chat with a contact (-1: use default value)
67 //@SRZNonDefaultOnly TriOption confAutoJoin; // automatically join the conference when we're going online
85 struct AccountConfig
{
86 string nick
; // my nick
87 string statusmsg
; // my status message
88 bool showOffline
; // show offline persons?
89 bool showPopup
; // show popups for messages?
90 bool blinkActivity
; // blink tray icon if we have some activity (unread msg, transfer request, etc.)?
91 bool skipUnread
; // skip contacts in `next_unread` command?
92 bool hideEmptyGroups
; // hide empty groups? (can be overriden by `hideNoVisible` group option)
93 bool ftranAllowed
; // file transfers allowed for this group (-1: use default value)
94 int resendRotDays
; // how many days message should be in "resend queue" if contact is offline
95 int hmcOnOpen
; // how many history messages we should show when we opening a chat with a contact
100 uint gid
; // group id; there is always group with gid 0, it is "common" default group
101 string name
; // group name
102 string note
; // group notes/description
103 bool opened
; // is this group opened?
104 TriOption showOffline
= TriOption
.Default
; // show offline persons in this group
105 TriOption showPopup
= TriOption
.Default
; // show popups for messages from this group
106 TriOption blinkActivity
= TriOption
.Default
; // blink tray icon if we have some activity (unread msg, transfer request, etc.) for this group
107 TriOption skipUnread
= TriOption
.Default
; // skip contacts from this group in `next_unread` command
108 TriOption hideIfNoVisibleMembers
= TriOption
.Default
; // hide this group if there are no visible items in it
109 TriOption ftranAllowed
; // file transfers allowed for this group
110 int resendRotDays
= -1; // how many days message should be in "resend queue" if contact is offline (-1: use default value)
111 int hmcOnOpen
= -1; // how many history messages we should show when we opening a chat with a contact (-1: use default value)
119 // pending authorization acceptance
120 // statusmsg: request text
121 // lastonlinetime: request time
123 // auth requested, awaiting acceptance
124 // statusmsg: request text
125 // lastonlinetime: request time
126 // this will change to `Friend` when contact gets online (it means that auth is accepted)
128 // this contact was deleted and put in "total ignore" mode
129 // i.e. any activity from this contact (especially auth requests) will be silently dropped on the floor
132 uint gid
; // group id (see groups.rc)
133 string nick
; // empty: unauthorized
134 @SRZNonDefaultOnly string visnick
; // empty: use `nick`
135 @SRZNonDefaultOnly string statusmsg
; // request message for pending auth request
136 @SRZNonDefaultOnly uint lastonlinetime
; // local unixtime; changed when contact status changed between offline and online (any kind of online)
137 Kind kind
= Kind
.Friend
;
138 string note
; // contact notes/description
139 PubKey pubkey
; // used as unique contact id, same as directory name
140 uint nospam
; // used only in `toxCoreLoadDataFile()`
141 @SRZNonDefaultOnly ToxAddr fraddr
; // as address includes nospam, save it too
146 // ////////////////////////////////////////////////////////////////////////// //
147 void syncFile(bool fullsync
=false) (Imp
!"core.stdc.stdio".FILE
* fl
) nothrow @nogc {
149 import core
.stdc
.stdio
: fileno
;
152 import core
.sys
.posix
.unistd
: fsync
, fdatasync
;
153 static if (fullsync
) fsync(fd
); else fdatasync(fd
);
159 // ////////////////////////////////////////////////////////////////////////// //
160 // write to temporary file, then atomically replace
161 bool serialize(T
) (in auto ref T v
, string fname
) {
162 import core
.stdc
.stdio
: FILE
, fopen
, fclose
, rename
;
163 import core
.sys
.posix
.unistd
: unlink
;
164 import std
.internal
.cstring
;
166 string tmpfname
= fname
~".$$$";
167 FILE
* fo
= fopen(tmpfname
.tempCString
, "w");
168 if (fo
is null) return false;
171 v
.txtser(VFile(fo
, own
:false), skipstname
:true);
172 } catch (Exception e
) {
174 unlink(tmpfname
.tempCString
);
180 if (rename(tmpfname
.tempCString
, fname
.tempCString
) != 0) {
181 unlink(tmpfname
.tempCString
);
189 // ////////////////////////////////////////////////////////////////////////// //
191 log format (it looks like text, but it isn't text):
192 T[YYYY/MM/DD HH:NN:SS]: text-in-utf8
194 text: chars in range of [0..31, 92, 127] are \xHH-encoded
196 !: server/app notification
199 if char before text is '!' instead of space, this is "/me" message
201 static struct LogFile
{
203 // this struct must not outlive LogFile
205 enum Kind
{ Notification
, Incoming
, Outgoing
}
207 bool isMe
; // "/me" message?
209 char[] rawText
; // undecoded
212 string
text () const {
213 import iv
.utfutil
: utf8Encode
;
215 text
.reserve(rawText
.length
);
217 foreach (dchar dc
; byDChar
) {
219 auto len
= utf8Encode(buf
[], dc
);
222 return cast(string
)text
; // it is safe to cast here
225 // decode raw text to dchars; returns forward range
226 auto byDChar () const nothrow @safe @nogc {
227 static struct Range
{
228 pure nothrow @trusted @nogc:
229 const(char)[] raw
; // anchored
231 @property bool empty () const => (pos
>= raw
.length
);
232 @property dchar front () const {
233 if (pos
< raw
.length
) {
234 char ch
= raw
.ptr
[pos
];
236 if (ch
!= '\\') return ch
;
237 if (raw
.length
-pos
< 2) return ch
;
239 if (ch
!= 'x' && ch
!= 'X') {
241 case 'e': return '\e';
242 case 'n': return '\n';
243 case 'r': return '\r';
244 case 't': return '\t';
249 if (raw
.length
-pos
< 4) return '?';
250 int n0
= raw
.ptr
[pos
+2].digitInBase(16);
251 if (n0
< 0) return '?';
252 int n1
= raw
.ptr
[pos
+3].digitInBase(16);
253 if (n1
< 0) return '?';
254 return cast(char)(n0
*16+n1
);
258 while (!dc
.decode(cast(ubyte)raw
.ptr
[cpos
++])) {
259 if (cpos
>= raw
.length
) break; // no more chars
261 return (dc
.complete ? dc
.codepoint
: dc
.replacement
);
263 return Utf8DecoderFast
.replacement
;
267 if (pos
< raw
.length
) {
268 char ch
= raw
.ptr
[pos
];
271 if (ch
!= '\\') return;
272 if (raw
.length
-pos
< 1) { pos
= raw
.length
; return; }
274 if (ch
!= 'x' && ch
!= 'X') return;
275 if (raw
.length
-pos
< 2) { pos
= raw
.length
; return; }
280 while (!dc
.decode(cast(ubyte)raw
.ptr
[pos
++])) {
281 if (pos
>= raw
.length
) break; // no more chars
285 Range
save () const => Range(raw
, pos
);
287 return Range(rawText
, 0);
298 @disable this (this); // no copies
299 void opAssign() (in auto ref LogFile lf
) { static assert(0, "no copies!"); }
301 this (const(char)[] fname
) nothrow => load(fname
);
303 // no `Msg` should outlive this call!
304 void clear () nothrow {
309 void load (const(char)[] fname
) nothrow {
310 import std
.internal
.cstring
;
312 if (messages
.length
) {
314 messages
.assumeSafeAppend
;
318 data
.assumeSafeAppend
;
321 if (fname
.length
== 0) return;
324 auto fi
= VFile(fname
);
325 if (fi
.size
> int.max
/8) assert(0, "log file too big");
326 data
.length
= cast(int)fi
.size
;
327 fi
.rawReadExact(data
);
328 } catch (Exception e
) {}
333 // `data` should be set
334 private void parseData () nothrow {
335 import core
.stdc
.string
: memchr
;
337 assert(messages
.length
== 0); // sorry
338 if (data
.length
== 0) return;
344 while (dp
< data
.ptr
+data
.length
) {
345 auto nx
= cast(char*)memchr(dp
, '\n', data
.length
-(dp
-data
.ptr
));
346 if (nx
is null) break;
351 messages
.reserve(nlcount
+1);
353 static int toInt (const(char)[] s
) nothrow @trusted @nogc {
355 while (s
.length
&& s
.ptr
[0].isdigit
) {
356 res
= res
*10+s
.ptr
[0]-'0';
363 auto dp
= data
.ptr
, nx
= data
.ptr
+data
.length
;
364 for (; dp
< data
.ptr
+data
.length
; dp
= nx
+1) {
365 nx
= cast(char*)memchr(dp
, '\n', data
.length
-(dp
-data
.ptr
));
366 if (nx
is null) nx
= data
.ptr
+data
.length
;
367 // 25 is the minimal message length
368 if (nx
-dp
< 25) continue;
369 if (dp
[1] != '[' || dp
[6] != '/' || dp
[9] != '/' || dp
[12] != ' ' || dp
[15] != ':' || dp
[18] != ':' || dp
[21] != ']' || dp
[22] != ':') continue;
370 foreach (immutable cidx
, immutable char ch
; dp
[0..21]) {
371 if (cidx
< 2 || cidx
== 6 || cidx
== 9 || cidx
== 12 || cidx
== 15 || cidx
== 18) continue;
372 if (!ch
.isdigit
) continue;
377 case '!': msg
.kind
= msg
.Kind
.Notification
; break;
378 case '<': msg
.kind
= msg
.Kind
.Outgoing
; break;
379 case '>': msg
.kind
= msg
.Kind
.Incoming
; break;
384 case ' ': msg
.isMe
= false; break;
385 case '!': msg
.isMe
= true; break;
388 int year
= toInt(dp
[2..6]);
389 if (year
< 2018 || year
> 2036) continue; //FIXME in 2036! ;-)
390 int month
= toInt(dp
[7..9]);
391 if (month
< 1 || month
> 12) continue;
392 int day
= toInt(dp
[10..12]);
393 if (day
< 1 || day
> 31) continue;
394 int hour
= toInt(dp
[13..15]);
395 if (hour
< 0 || hour
> 23) continue;
396 int minute
= toInt(dp
[16..18]);
397 if (minute
< 0 || minute
> 59) continue;
398 int second
= toInt(dp
[19..21]);
399 if (second
< 0 || second
> 59) continue;
402 msg
.time
= SysTime(DateTime(year
, month
, day
, hour
, minute
, second
));
404 } catch (Exception e
) {}
405 if (!timeok
) continue;
406 msg
.rawText
= dp
[24..(nx
-dp
)];
412 // append line to log file; text is in utf8
413 // WARNING! does no validity checks!
414 static bool appendLine (const(char)[] fname
, LogFile
.Msg
.Kind kind
, const(char)[] text
, bool isMe
, SysTime time
) nothrow {
415 import core
.stdc
.stdio
: FILE
, fopen
, fclose
, fwrite
;
416 import std
.internal
.cstring
;
418 if (fname
.length
== 0) return false;
420 FILE
* fo
= fopen(fname
.tempCString
, "a");
421 if (fo
is null) return false;
422 scope(exit
) { syncFile(fo
); fclose(fo
); }
424 bool xwrite (const(char)[] s
...) nothrow @nogc {
425 while (s
.length
> 0) {
426 auto wr
= fwrite(s
.ptr
, 1, s
.length
, fo
);
428 import core
.stdc
.errno
;
429 if (errno
!= EINTR
) return false;
437 bool wrnum (int n
, int wdt
) nothrow @nogc {
438 import core
.stdc
.stdio
: snprintf
;
439 if (n
< 0) return false; // alas
440 char[128] buf
= void;
441 auto len
= (wdt
> 0 ?
snprintf(buf
.ptr
, buf
.length
, "%0*d", wdt
, n
) : snprintf(buf
.ptr
, buf
.length
, "%d", n
));
442 if (len
< 1) return false;
443 return xwrite(buf
[0..len
]);
446 final switch (kind
) {
447 case LogFile
.Msg
.Kind
.Notification
: if (!xwrite("!")) return false; break;
448 case LogFile
.Msg
.Kind
.Incoming
: if (!xwrite(">")) return false; break;
449 case LogFile
.Msg
.Kind
.Outgoing
: if (!xwrite("<")) return false; break;
453 auto dt = cast(DateTime
)time
;
454 if (!xwrite("[")) return false;
455 if (!wrnum(dt.year
, 4)) return false;
456 if (!xwrite("/")) return false;
457 if (!wrnum(cast(int)dt.month
, 2)) return false;
458 if (!xwrite("/")) return false;
459 if (!wrnum(dt.day
, 2)) return false;
460 if (!xwrite(" ")) return false;
461 if (!wrnum(dt.hour
, 2)) return false;
462 if (!xwrite(":")) return false;
463 if (!wrnum(dt.minute
, 2)) return false;
464 if (!xwrite(":")) return false;
465 if (!wrnum(dt.second
, 2)) return false;
466 if (!xwrite("]:")) return false;
467 if (!xwrite(isMe ?
"!" : " ")) return false;
469 // write encoded string
470 while (text
.length
) {
472 while (end
< text
.length
) {
473 char ch
= text
.ptr
[end
];
474 if (ch
< ' ' || ch
== 92 || ch
== 127) break;
478 if (!xwrite(text
[0..end
])) return false;
481 switch (text
.ptr
[0]) {
482 case '\e': if (!xwrite(`\e`)) return false; break;
483 case '\n': if (!xwrite(`\n`)) return false; break;
484 case '\r': if (!xwrite(`\r`)) return false; break;
485 case '\t': if (!xwrite(`\t`)) return false; break;
487 import core
.stdc
.stdio
: snprintf
;
489 auto len
= snprintf(buf
.ptr
, buf
.length
, "\\x%02x", cast(uint)text
.ptr
[0]);
490 if (!xwrite(buf
[0..len
])) return false;
500 // append line to log file; text is in utf8
501 // WARNING! does no validity checks!
502 static bool appendLine (const(char)[] fname
, LogFile
.Msg
.Kind kind
, const(char)[] text
, bool isMe
=false) nothrow {
504 return appendLine(fname
, kind
, text
, isMe
, systimeNow
);
505 } catch (Exception e
) {}