command console is visible when activated
[bioacid.git] / toxproto.d
blob0a5c3adc4391de5c3cb75e53d472d8b16b5b4d8c
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 // Tox protocol thread
18 // Each Tox connection spawns a thread that does all the communication.
19 // Incoming messages are posted to [glconCtlWindow].
20 // Asking for outgoing actions are done with public interface.
21 // No tox internals are exposed to the outer world.
22 module toxproto is aliced;
23 private:
25 import core.time;
27 import std.concurrency;
28 import std.datetime;
30 import iv.cmdcon;
31 import iv.cmdcon.gl;
32 import iv.strex;
33 import iv.tox;
34 import iv.txtser;
35 import iv.unarray;
36 import iv.utfutil;
37 import iv.vfs;
38 import iv.vfs.util;
40 import accdb;
42 //version = ToxCoreDebug;
45 // ////////////////////////////////////////////////////////////////////////// //
46 public:
49 // ////////////////////////////////////////////////////////////////////////// //
50 class ProtocolException : Exception {
51 this (string msg, string file=__FILE__, usize line=__LINE__, Throwable next=null) pure nothrow @safe @nogc {
52 super(msg, file, line, next);
57 // ////////////////////////////////////////////////////////////////////////// //
58 // messages thread sends to [glconCtlWindow].
59 // [who] can be the same as [self] to indicate account state changes
61 class ToxEventBase {
62 PubKey self; // account
63 PubKey who; // can be same as [self]
64 nothrow @trusted @nogc:
65 this (in ref PubKey aself, in ref PubKey awho) { self[] = aself[]; who = awho[]; }
68 // connection state changed
69 class ToxEventConnection : ToxEventBase {
70 bool connected;
71 nothrow @trusted @nogc:
72 this (in ref PubKey aself, in ref PubKey awho, bool aconnected) { super(aself, awho); connected = aconnected; }
75 // online status changed
76 class ToxEventStatus : ToxEventBase {
77 ContactStatus status;
78 nothrow @trusted @nogc:
79 this (in ref PubKey aself, in ref PubKey awho, ContactStatus astatus) { super(aself, awho); status = astatus; }
82 // nick changed
83 class ToxEventNick : ToxEventBase {
84 string nick; // new nick
85 nothrow @trusted @nogc:
86 this (in ref PubKey aself, in ref PubKey awho, string anick) { super(aself, awho); nick = anick; }
89 // status message changed
90 class ToxEventStatusMsg : ToxEventBase {
91 string message; // new message
92 nothrow @trusted @nogc:
93 this (in ref PubKey aself, in ref PubKey awho, string amessage) { super(aself, awho); message = amessage; }
96 // typing status changed
97 class ToxEventTyping : ToxEventBase {
98 bool typing;
99 nothrow @trusted @nogc:
100 this (in ref PubKey aself, in ref PubKey awho, bool atyping) { super(aself, awho); typing = atyping; }
103 // new message comes
104 class ToxEventMessage : ToxEventBase {
105 bool action; // is this an "action" message? (/me)
106 string message;
107 SysTime time;
109 this (in ref PubKey aself, in ref PubKey awho, bool aaction, string amessage) {
110 super(aself, awho);
111 action = aaction;
112 message = amessage;
113 time = Clock.currTime;
116 this (in ref PubKey aself, in ref PubKey awho, bool aaction, string amessage, SysTime atime) {
117 super(aself, awho);
118 action = aaction;
119 message = amessage;
120 time = atime;
124 // send message ack comes
125 class ToxEventMessageAck : ToxEventBase {
126 long msgid;
127 nothrow @trusted @nogc:
128 this (in ref PubKey aself, in ref PubKey awho, long amsgid) { super(aself, awho); msgid = amsgid; }
131 // friend request comes
132 class ToxEventFriendReq : ToxEventBase {
133 string message; // request message
134 nothrow @trusted @nogc:
135 this (in ref PubKey aself, in ref PubKey awho, string amessage) { super(aself, awho); message = amessage; }
139 // ////////////////////////////////////////////////////////////////////////// //
141 /// shutdown protocol module
142 void toxCoreShutdownAll () {
143 int acksLeft = 0;
144 synchronized(TPInfo.classinfo) {
145 foreach (TPInfo ti; allProtos) {
146 ti.tid.send(TrdCmdQuit.init);
147 ++acksLeft;
150 while (acksLeft > 0) {
151 receive(
152 (TrdCmdQuitAck ack) { --acksLeft; },
153 (Variant v) {},
157 foreach (TPInfo tpi; allProtos) {
158 // save toxcode data, if there is any
159 if (tpi.tox !is null && tpi.toxDataDiskName.length) saveToxCoreData(tpi.tox, tpi.toxDataDiskName);
160 if (tpi.tox !is null) { tox_kill(tpi.tox); tpi.tox = null; }
165 // ////////////////////////////////////////////////////////////////////////// //
168 static immutable PubKey toxCoreEmptyKey = 0;
170 /// delegate that will be used to send messages.
171 /// can be called from any thread, so it should be thread-safe, and should avoid deadlocks.
172 /// the delegate should be set on program startup, and should not be changed anymore.
173 /// FIXME: add API to change this!
174 __gshared void delegate (Object msg) nothrow toxCoreSendEvent;
177 bool isValidKey (in ref PubKey key) nothrow @safe @nogc => (key[] != toxCoreEmptyKey[]);
180 /// returns public key for account with the given data file, or [toxCoreEmptyKey].
181 PubKey toxCoreGetAccountPubKey (const(char)[] toxdatafname) nothrow {
182 TPInfo tacc = null;
183 synchronized(TPInfo.classinfo) {
184 foreach (TPInfo ti; allProtos) if (ti.toxDataDiskName == toxdatafname) { tacc = ti; break; }
185 // not found?
186 if (tacc is null) {
187 // nope, not found, try to open account
188 ProtoOptions protoOpts;
189 auto tox = toxCreateInstance(toxdatafname, protoOpts, allowNew:false);
190 if (tox is null) return toxCoreEmptyKey;
191 PubKey self; tox_self_get_public_key(tox, self.ptr);
192 // and immediately kill it, or it will go online. fuck.
193 tox_kill(tox);
194 return self[];
197 if (tacc !is null) {
198 synchronized(tacc) {
199 PubKey self; tox_self_get_public_key(tacc.tox, self.ptr);
200 return self[];
203 return toxCoreEmptyKey;
207 /// checks if we have a working thread for `self`.
208 bool toxCoreIsAccountOpen (in ref PubKey self) nothrow {
209 synchronized(TPInfo.classinfo) {
210 foreach (TPInfo ti; allProtos) {
211 if (ti.self[] == self[]) return true;
214 return false;
218 /// returns nick for the given account, or `null`.
219 string toxCoreGetNick (in ref PubKey self) nothrow {
220 synchronized(TPInfo.classinfo) {
221 foreach (TPInfo ti; allProtos) {
222 if (ti.self[] == self[]) return ti.nick;
225 return null;
229 /// creates new Tox account (or opens old), stores it in data file, returns public key for new account.
230 /// returns [toxCoreEmptyKey] on error.
231 /// TODO: pass protocol options here
232 /// WARNING! don't call this from more than one thread with the same `toxdatafname`
233 PubKey toxCoreCreateAccount (const(char)[] toxdatafname, const(char)[] nick) nothrow {
234 if (toxdatafname.length == 0) return toxCoreEmptyKey;
236 if (nick.length == 0 || nick.length > tox_max_name_length()) return toxCoreEmptyKey;
238 try {
239 import std.path : absolutePath;
241 string toxDataDiskName = toxdatafname.idup.absolutePath;
243 synchronized(TPInfo.classinfo) {
244 foreach (TPInfo ti; allProtos) {
245 if (ti.toxDataDiskName == toxDataDiskName) return ti.self[];
249 ProtoOptions protoOpts;
250 protoOpts.udp = true;
252 auto tox = toxCreateInstance(toxdatafname, protoOpts, allowNew:true);
253 if (tox is null) return toxCoreEmptyKey;
255 auto ti = new TPInfo();
256 ti.toxDataDiskName = toxDataDiskName;
257 tox_self_get_public_key(tox, ti.self.ptr);
258 ti.nick = nick.idup;
260 // set user name
261 tox_self_set_name(tox, nick.ptr, nick.length);
262 saveToxCoreData(tox, toxDataDiskName);
264 // and immediately kill it, or it will go online. fuck.
265 tox_kill(tox);
267 synchronized(TPInfo.classinfo) {
268 allProtos ~= ti;
269 startThread(ti);
271 return ti.self[];
272 } catch (Exception e) {
275 return toxCoreEmptyKey;
279 /// starts working thread for account with the given data file.
280 /// returns account public key, or [toxCoreEmptyKey] on error.
281 /// TODO: pass protocol options here
282 /// WARNING! don't call this from more than one thread with the same `toxdatafname`
283 PubKey toxCoreOpenAccount (const(char)[] toxdatafname) nothrow {
284 if (toxdatafname.length == 0) return toxCoreEmptyKey;
286 try {
287 import std.path : absolutePath, dirName;
289 string toxDataDiskName = toxdatafname.idup.absolutePath;
291 synchronized(TPInfo.classinfo) {
292 foreach (TPInfo ti; allProtos) {
293 if (ti.toxDataDiskName == toxDataDiskName) return ti.self[];
297 ProtoOptions protoOpts;
298 try {
299 protoOpts.txtunser(VFile(toxDataDiskName.dirName~"/proto.rc"));
300 } catch (Exception e) {
301 protoOpts = protoOpts.default;
302 protoOpts.udp = true;
306 auto tox = toxCreateInstance(toxdatafname, protoOpts, allowNew:false);
307 if (tox is null) return toxCoreEmptyKey;
308 PubKey self; tox_self_get_public_key(tox, self.ptr);
310 auto ti = new TPInfo();
311 ti.toxDataDiskName = toxDataDiskName;
312 tox_self_get_public_key(tox, ti.self.ptr);
314 // get user name
315 auto nsz = tox_self_get_name_size(tox);
316 if (nsz != 0) {
317 if (nsz > tox_max_name_length()) nsz = tox_max_name_length(); // just in case
318 // k8: toxcore developers are idiots, so we have to do dynalloc here
319 auto xbuf = new char[](tox_max_name_length());
320 xbuf[] = 0;
321 tox_self_get_name(tox, xbuf.ptr);
322 ti.nick = cast(string)(xbuf[0..nsz]); // ah, cast it directly here
325 // and immediately kill it, or it will go online. fuck.
326 tox_kill(tox);
328 synchronized(TPInfo.classinfo) {
329 allProtos ~= ti;
330 startThread(ti);
332 return self[];
333 } catch (Exception e) {
336 return toxCoreEmptyKey;
340 /// stops working thread for account with the given data file.
341 /// returns success flag.
342 bool toxCoreCloseAccount (in ref PubKey self) nothrow {
343 try {
344 TPInfo tpi = null;
345 synchronized(TPInfo.classinfo) {
346 foreach (TPInfo ti; allProtos) if (ti.self[] == self[]) { tpi = ti; break; }
348 if (tpi is null) return false;
350 // send "quit" signal
351 tpi.tid.send(TrdCmdQuit.init);
352 for (;;) {
353 bool done = false;
354 receive(
355 (TrdCmdQuitAck ack) { done = true; },
356 (Variant v) {},
358 if (done) break;
361 // save toxcode data, if there is any
362 if (tpi.tox !is null && tpi.toxDataDiskName.length) saveToxCoreData(tpi.tox, tpi.toxDataDiskName);
363 if (tpi.tox !is null) { tox_kill(tpi.tox); tpi.tox = null; }
365 // remove from the list
366 synchronized(TPInfo.classinfo) {
367 foreach (immutable idx, TPInfo ti; allProtos) {
368 if (ti.self[] == self[]) {
369 foreach (immutable c; idx+1..allProtos.length) allProtos[c-1] = allProtos[c];
370 delete allProtos[$-1];
371 allProtos.length -= 1;
372 allProtos.assumeSafeAppend;
373 break;
378 return true;
379 } catch (Exception e) {
381 return false;
385 enum MsgIdError = -1;
386 enum MsgIdOffline = 0;
388 /// sends message. returns message id that can be used to track acks.
389 /// returns [MsgIdError] for unknown account, or unknown `dest`.
390 /// returns [MsgIdOffline] if `self` or `dest` is offline (message is not queued to send in this case).
391 /// note that `0` is a not a valid message id.
392 /// cannot send empty messages, and messages longer than `TOX_MAX_MESSAGE_LENGTH` chars.
393 long toxCoreSendMessage (in ref PubKey self, in ref PubKey dest, const(char)[] msg, bool action=false) nothrow {
394 long res = MsgIdError;
395 if (msg.length == 0) return res; // cannot send empty messages
396 TOX_MESSAGE_TYPE tt = (action ? TOX_MESSAGE_TYPE_ACTION : TOX_MESSAGE_TYPE_NORMAL);
398 if (msg.startsWith("/me ")) {
399 msg = msg[4..$].xstripleft;
400 if (msg.length == 0) return res; // cannot send empty messages
401 tt = TOX_MESSAGE_TYPE_ACTION;
404 if (msg.length > tox_max_message_length()) return res; // message too long
405 doWithLockedTPByKey(self, delegate (ti) {
406 if (ti.tox is null) { res = MsgIdOffline; return; }
407 //if (tox_self_get_connection_status(ti.tox) == TOX_CONNECTION_NONE) { res = MsgIdOffline; return; }
408 // find friend
409 uint frnum = tox_friend_by_public_key(ti.tox, dest.ptr);
410 if (frnum == uint.max) return; // error
411 // offline?
412 //if (tox_friend_get_connection_status(ti.tox, frnum) == TOX_CONNECTION_NONE) { res = MsgIdOffline; return; }
413 // nope, online; queue the message
414 TOX_ERR_FRIEND_SEND_MESSAGE err = 0;
415 uint msgid = tox_friend_send_message(ti.tox, frnum, tt, msg.ptr, msg.length, &err);
416 switch (err) {
417 case TOX_ERR_FRIEND_SEND_MESSAGE_OK: res = (cast(long)msgid)+1; break;
418 case TOX_ERR_FRIEND_SEND_MESSAGE_NULL: res = MsgIdError; break;
419 case TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_FOUND: res = MsgIdError; break;
420 case TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_CONNECTED: res = MsgIdOffline; break;
421 case TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ: res = MsgIdError; break;
422 case TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG: res = MsgIdError; break;
423 case TOX_ERR_FRIEND_SEND_MESSAGE_EMPTY: res = MsgIdError; break;
424 default: res = MsgIdError; break;
426 if (res > 0) ti.ping();
428 return res;
432 /// sets new status.
433 /// returns `false` if `self` is invalid, or some error occured.
434 bool toxCoreSetStatus (in ref PubKey self, ContactStatus status) nothrow {
435 if (status == ContactStatus.Connecting) return false; // oops
436 bool res = false;
437 doWithLockedTPByKey(self, delegate (ti) {
438 // if we're going offline, kill toxcore instance
439 if (status == ContactStatus.Offline) {
440 ti.tid.send(TrdCmdKillToxCore.init);
441 // send messages, just in case
442 if (toxCoreSendEvent !is null) {
443 toxCoreSendEvent(new ToxEventStatus(ti.self, ti.self, ContactStatus.Offline));
444 toxCoreSendEvent(new ToxEventConnection(ti.self, ti.self, false));
446 res = true;
447 return;
450 ToxP tox;
452 // want to go online
453 if (ti.tox is null) {
454 // need to create toxcore object
455 try {
456 import std.path : absolutePath, dirName;
458 ProtoOptions protoOpts;
459 try {
460 protoOpts.txtunser(VFile(ti.toxDataDiskName.dirName~"/proto.rc"));
461 } catch (Exception e) {
462 protoOpts = protoOpts.default;
463 protoOpts.udp = true;
466 conwriteln("creating ToxCore...");
467 tox = toxCreateInstance(ti.toxDataDiskName, protoOpts, allowNew:false);
468 if (tox is null) {
469 conwriteln("can't create ToxCore...");
470 return;
472 TrdCmdSetToxCore cmd;
473 cmd.tox = tox;
474 ti.tid.send(cast(shared)cmd);
475 } catch (Exception e) {
476 return;
478 } else {
479 tox = ti.tox;
481 assert(tox !is null);
483 TOX_USER_STATUS st;
484 final switch (status) {
485 case ContactStatus.Offline: assert(0, "wtf?!");
486 case ContactStatus.Online: st = TOX_USER_STATUS_NONE; break;
487 case ContactStatus.Away: st = TOX_USER_STATUS_AWAY; break;
488 case ContactStatus.Busy: st = TOX_USER_STATUS_BUSY; break;
489 case ContactStatus.Connecting: assert(0, "wtf?!");
491 tox_self_set_status(tox, st);
492 ti.needSave = true;
493 res = true;
494 ti.ping();
496 return res;
500 /// sets new status message.
501 /// returns `false` if `self` is invalid, message is too long, or on any other error.
502 bool toxCoreSetStatusMessage (in ref PubKey self, const(char)[] message) nothrow {
503 if (message.length > tox_max_status_message_length()) return false;
504 bool res = false;
505 doWithLockedTPByKey(self, delegate (ti) {
506 if (ti.tox is null) return;
507 res = tox_self_set_status_message(ti.tox, message.ptr, message.length);
508 if (res) ti.needSave = true;
509 if (res) ti.ping();
511 return res;
515 /// sends friend request.
516 /// returns `false` if `self` is invalid, message is too long, or on any other error.
517 bool toxCoreSendFriendRequest (in ref PubKey self, in ref ubyte[TOX_ADDRESS_SIZE] dest, const(char)[] message) nothrow {
518 if (message.length > tox_max_friend_request_length()) return false; // cannot send long requests
519 bool res = false;
520 doWithLockedTPByKey(self, delegate (ti) {
521 if (ti.tox is null) return; // this account is offline
522 //if (tox_self_get_connection_status(ti.tox) == TOX_CONNECTION_NONE) return; // this account is offline
523 // find friend
524 uint frnum = tox_friend_by_public_key(ti.tox, dest.ptr);
525 if (frnum != uint.max) {
526 // we already has such friend, do nothing
527 res = true;
528 return;
530 frnum = tox_friend_add(ti.tox, dest.ptr, message.ptr, message.length);
531 res = (frnum != uint.max);
532 if (res) ti.needSave = true;
533 if (res) ti.ping();
535 return res;
539 /// unconditionally adds a friend.
540 /// returns `false` if `self` is invalid, message is too long, friend not found, or on any other error.
541 bool toxCoreAddFriend (in ref PubKey self, in ref PubKey dest) nothrow {
542 bool res = false;
543 doWithLockedTPByKey(self, delegate (ti) {
544 if (ti.tox is null) return; // this account is offline
545 //if (tox_self_get_connection_status(ti.tox) == TOX_CONNECTION_NONE) return; // this account is offline
546 // find friend
547 uint frnum = tox_friend_by_public_key(ti.tox, dest.ptr);
548 if (frnum != uint.max) {
549 // we already has such friend, do nothing
550 res = true;
551 return;
553 frnum = tox_friend_add_norequest(ti.tox, dest.ptr);
554 res = (frnum != uint.max);
555 if (res) ti.needSave = true;
556 if (res) ti.ping();
558 return res;
562 /// unconditionally removes a friend.
563 /// returns `false` if `self` is invalid, message is too long, friend not found, or on any other error.
564 bool toxCoreRemoveFriend (in ref PubKey self, in ref PubKey dest) nothrow {
565 bool res = false;
566 doWithLockedTPByKey(self, delegate (ti) {
567 if (ti.tox is null) return; // this account is offline
568 //if (tox_self_get_connection_status(ti.tox) == TOX_CONNECTION_NONE) return; // this account is offline
569 // find friend
570 uint frnum = tox_friend_by_public_key(ti.tox, dest.ptr);
571 if (frnum == uint.max) { res = false; return; } // no such friend
572 res = tox_friend_delete(ti.tox, frnum);
573 if (res) ti.needSave = true;
574 if (res) ti.ping();
576 return res;
580 /// checks if the given accound has a friend with the given pubkey.
581 /// returns `false` if `self` is invalid or offline, or on any other error, or if there is no such friend.
582 bool toxCoreHasFriend (in ref PubKey self, in ref PubKey dest) nothrow {
583 bool res = false;
584 doWithLockedTPByKey(self, delegate (ti) {
585 if (ti.tox is null) return; // this account is offline
586 // find friend
587 uint frnum = tox_friend_by_public_key(ti.tox, dest.ptr);
588 res = (frnum != uint.max);
590 return res;
594 /// calls delegate for each known friend.
595 /// return `true` from delegate to stop.
596 /// WARNING! all background operations are locked, so don't spend too much time in delegate!
597 void toxCoreForEachFriend (in ref PubKey self, scope bool delegate (in ref PubKey self, in ref PubKey frpub, scope const(char)[] nick) dg) nothrow {
598 if (dg is null) return;
599 doWithLockedTPByKey(self, delegate (ti) {
600 if (ti.tox is null) return; // this account is offline
601 auto frcount = tox_self_get_friend_list_size(ti.tox);
602 if (frcount == 0) return;
603 auto list = new uint[](frcount);
604 scope(exit) delete list;
605 char[] nick;
606 scope(exit) delete nick;
607 tox_self_get_friend_list(ti.tox, list.ptr);
608 PubKey fpk;
609 foreach (immutable fidx, immutable fid; list[]) {
610 if (!tox_friend_get_public_key(ti.tox, fid, fpk.ptr)) continue;
611 auto nsz = tox_friend_get_name_size(ti.tox, fid);
612 if (nsz > nick.length) nick.length = nsz;
613 if (nsz != 0) tox_friend_get_name(ti.tox, fid, nick.ptr);
614 try {
615 dg(self, fpk, nick[0..nsz]);
616 } catch (Exception e) {
617 break;
624 /// calls delegate with ToxP. ugly name is intentional.
625 void toxCoreCallWithToxP (in ref PubKey self, scope void delegate (ToxP tox) dg) nothrow {
626 if (dg is null) return;
627 doWithLockedTPByKey(self, delegate (ti) {
628 if (ti.tox is null) return; // this account is offline
629 try {
630 dg(ti.tox);
631 } catch (Exception e) {}
636 // ////////////////////////////////////////////////////////////////////////// //
637 // private ToxCore protocol implementation details
638 private:
640 class TPInfo {
641 static struct FileRejectEvent {
642 uint frnum;
643 uint flnum;
646 //ToxProtocol pr;
647 string toxDataDiskName;
648 ToxP tox;
649 Tid tid;
650 PubKey self;
651 string nick;
652 FileRejectEvent[] rej;
653 bool doBootstrap; // do bootstrapping
654 bool needSave;
656 void ping () nothrow {
657 if (tox is null) return;
658 try { tid.send(TrdCmdPing.init); } catch (Exception e) {}
662 __gshared TPInfo[] allProtos;
665 // ////////////////////////////////////////////////////////////////////////// //
666 struct TrdCmdQuit {} // quit thread loop
667 struct TrdCmdQuitAck {} // quit thread loop
668 struct TrdCmdPing {} // do something
669 struct TrdCmdKillToxCore {}
670 struct TrdCmdSetToxCore { ToxP tox; }
673 // ////////////////////////////////////////////////////////////////////////// //
674 // this should be called with registered `ti`
675 void startThread (TPInfo ti) {
676 assert(ti !is null);
677 ti.tid = spawn(&toxCoreThread, thisTid, *cast(immutable(void)**)&ti);
681 // ////////////////////////////////////////////////////////////////////////// //
682 static void toxCoreThread (Tid ownerTid, immutable(void)* tiptr) {
683 uint mswait = 0;
684 MonoTime lastSaveTime = MonoTime.zero;
685 TPInfo ti = *cast(TPInfo*)&tiptr; // HACK!
686 ToxP newTox = null;
687 try {
688 for (;;) {
689 bool doQuit = false;
690 bool doKillTox = false;
692 if (ti.tox !is null && ti.doBootstrap) {
693 conwriteln("TOX(", ti.nick, "): bootstrapping");
694 ti.doBootstrap = false;
695 bootstrap(ti);
696 mswait = tox_iteration_interval(ti.tox);
697 if (mswait < 1) mswait = 1;
698 conwriteln("TOX(", ti.nick, "): bootstrapping complete (mswait=", mswait, ")");
701 receiveTimeout((mswait ? mswait.msecs : 10.hours),
702 (TrdCmdQuit cmd) { doQuit = true; },
703 (TrdCmdPing cmd) {},
704 (TrdCmdKillToxCore cmd) { doKillTox = true; },
705 (shared TrdCmdSetToxCore cmd) { newTox = cast(ToxP)cmd.tox; },
706 (Variant v) { conwriteln("WUTAFUCK?! "); },
708 if (doQuit) break;
710 if (newTox !is null) {
711 conwriteln("TOX(", ti.nick, "): got new toxcore pointer");
712 if (ti.tox !is newTox) {
713 if (ti.tox !is null) tox_kill(ti.tox);
714 ti.tox = newTox;
716 newTox = null;
717 ti.doBootstrap = true;
718 mswait = 1;
719 continue;
722 if (ti.tox is null) { mswait = 0; continue; }
724 if (doKillTox) {
725 conwriteln("TOX(", ti.nick, "): killing toxcore pointer");
726 doKillTox = false;
727 if (ti.tox !is null) {
728 tox_kill(ti.tox);
729 ti.tox = null;
730 } else {
731 conwriteln("TOX(", ti.nick, "): wtf?!");
733 mswait = 0;
734 continue;
737 tox_iterate(ti.tox, null);
738 mswait = tox_iteration_interval(ti.tox);
739 //conwriteln("TOX(", ti.nick, "): interval is ", mswait);
740 if (mswait < 1) mswait = 1;
741 synchronized(ti) {
742 if (ti.needSave) {
743 auto ctt = MonoTime.currTime;
744 if ((ctt-lastSaveTime).total!"minutes" > 1) {
745 ti.needSave = false;
746 lastSaveTime = ctt;
747 saveToxCoreData(ti.tox, ti.toxDataDiskName);
752 ownerTid.send(TrdCmdQuitAck.init);
753 } catch (Throwable e) {
754 // here, we are dead and fucked (the exact order doesn't matter)
755 import core.stdc.stdlib : abort;
756 import core.stdc.stdio : fprintf, stderr;
757 import core.memory : GC;
758 import core.thread : thread_suspendAll;
759 GC.disable(); // yeah
760 thread_suspendAll(); // stop right here, you criminal scum!
761 auto s = e.toString();
762 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
763 abort(); // die, you bitch!
768 // ////////////////////////////////////////////////////////////////////////// //
769 // find TPInfo object for the given tox handle; used in callbacks
770 TPInfo findTP (ToxP tox) nothrow {
771 if (tox is null) return null;
772 synchronized(TPInfo.classinfo) {
773 foreach (TPInfo ti; allProtos) {
774 if (ti.tox is tox) return ti;
777 return null;
781 // returns `true` if found and executed without errors
782 bool doWithLockedTPByKey (in ref PubKey self, scope void delegate (TPInfo ti) dg) nothrow {
783 TPInfo tpi = null;
784 if (dg is null) return false;
785 synchronized(TPInfo.classinfo) {
786 foreach (TPInfo ti; allProtos) if (ti.self[] == self[]) { tpi = ti; break; }
788 if (tpi !is null) {
789 synchronized(tpi) {
790 try {
791 dg(tpi);
792 return true;
793 } catch (Exception e) {
794 try {
795 conprintfln("\nTOX CB EXCEPTION: %s\n\n", e.toString);
796 } catch (Exception e) {}
797 return false;
801 return false;
805 // returns `true` if found and executed without errors
806 bool doWithLockedTP (ToxP tox, scope void delegate (TPInfo ti) dg) nothrow {
807 TPInfo tpi = null;
808 if (tox is null || dg is null) return false;
809 synchronized(TPInfo.classinfo) {
810 foreach (TPInfo ti; allProtos) if (ti.tox is tox) { tpi = ti; break; }
812 if (tpi !is null) {
813 synchronized(tpi) {
814 try {
815 dg(tpi);
816 return true;
817 } catch (Exception e) {
818 try {
819 conprintfln("\nTOX CB EXCEPTION: %s\n\n", e.toString);
820 } catch (Exception e) {}
821 return false;
825 return false;
829 // ////////////////////////////////////////////////////////////////////////// //
830 // toxcore callbacks
832 // self connection state was changed
833 static extern(C) void connectionCB (Tox* tox, TOX_CONNECTION status, void* udata) nothrow {
834 if (toxCoreSendEvent is null) return;
835 ToxEventBase tcmsg;
836 if (!tox.doWithLockedTP(delegate (TPInfo ti) {
837 PubKey self; tox_self_get_public_key(tox, self.ptr);
838 conwriteln("TOX(", ti.nick, "): ", (status != TOX_CONNECTION_NONE ? "" : "dis"), "connected.");
840 tcmsg = new ToxEventConnection(self, self, status != TOX_CONNECTION_NONE);
841 })) return;
842 toxCoreSendEvent(tcmsg);
846 // friend connection state was changed
847 static extern(C) void friendConnectionCB (Tox* tox, uint frnum, TOX_CONNECTION status, void* udata) nothrow {
848 if (toxCoreSendEvent is null) return;
849 ToxEventBase tcmsg;
850 if (!tox.doWithLockedTP(delegate (TPInfo ti) {
851 PubKey self; tox_self_get_public_key(tox, self.ptr);
852 PubKey who; if (!tox_friend_get_public_key(tox, frnum, who.ptr)) return; // wtf?!
853 conwriteln("TOX(", ti.nick, "): friend #", frnum, " ", (status != TOX_CONNECTION_NONE ? "" : "dis"), "connected.");
855 tcmsg = new ToxEventConnection(self, who, status != TOX_CONNECTION_NONE);
856 if (status != TOX_CONNECTION_NONE) ti.needSave = true;
857 })) return;
858 toxCoreSendEvent(tcmsg);
862 static extern(C) void friendStatusCB (Tox* tox, uint frnum, TOX_USER_STATUS status, void* udata) nothrow {
863 if (toxCoreSendEvent is null) return;
864 ToxEventBase tcmsg;
865 if (!tox.doWithLockedTP(delegate (TPInfo ti) {
866 PubKey self; tox_self_get_public_key(tox, self.ptr);
867 PubKey who; if (!tox_friend_get_public_key(tox, frnum, who.ptr)) return; // wtf?!
868 conwriteln("TOX(", ti.nick, "): friend #", frnum, " changed status to ", status);
870 ContactStatus cst = ContactStatus.Offline;
871 switch (status) {
872 case TOX_USER_STATUS_NONE: cst = ContactStatus.Online; break;
873 case TOX_USER_STATUS_AWAY: cst = ContactStatus.Away; break;
874 case TOX_USER_STATUS_BUSY: cst = ContactStatus.Busy; break;
875 default: return;
877 tcmsg = new ToxEventStatus(self, who, cst);
878 })) return;
879 toxCoreSendEvent(tcmsg);
883 static extern(C) void friendNameCB (Tox* tox, uint frnum, const(char)* name, usize length, void* udata) nothrow {
884 if (toxCoreSendEvent is null) return;
885 ToxEventBase tcmsg;
886 if (!tox.doWithLockedTP(delegate (TPInfo ti) {
887 PubKey self; tox_self_get_public_key(tox, self.ptr);
888 PubKey who; if (!tox_friend_get_public_key(tox, frnum, who.ptr)) return; // wtf?!
889 conwriteln("TOX(", ti.nick, "): friend #", frnum, " changed name to <", name[0..length], ">");
891 tcmsg = new ToxEventNick(self, who, name[0..length].idup);
892 ti.needSave = true;
893 })) return;
894 toxCoreSendEvent(tcmsg);
898 static extern(C) void friendStatusMessageCB (Tox* tox, uint frnum, const(char)* msg, usize msglen, void* user_data) nothrow {
899 if (toxCoreSendEvent is null) return;
900 ToxEventBase tcmsg;
901 if (!tox.doWithLockedTP(delegate (TPInfo ti) {
902 PubKey self; tox_self_get_public_key(tox, self.ptr);
903 PubKey who; if (!tox_friend_get_public_key(tox, frnum, who.ptr)) return; // wtf?!
904 conwriteln("TOX(", ti.nick, "): friend #", frnum, " changed status to <", msg[0..msglen], ">");
906 tcmsg = new ToxEventStatusMsg(self, who, msg[0..msglen].idup);
907 ti.needSave = true;
908 })) return;
909 toxCoreSendEvent(tcmsg);
913 static extern(C) void friendReqCB (Tox* tox, const(ubyte)* pk, const(char)* msg, usize msglen, void* udata) nothrow {
914 if (toxCoreSendEvent is null) return;
915 ToxEventBase tcmsg;
916 if (!tox.doWithLockedTP(delegate (TPInfo ti) {
917 PubKey self; tox_self_get_public_key(tox, self.ptr);
918 PubKey who; who[] = pk[0..PubKey.length];
919 conwriteln("TOX(", ti.nick, "): friend request comes: <", msg[0..msglen], ">");
921 tcmsg = new ToxEventFriendReq(self, who, msg[0..msglen].idup);
922 ti.needSave = true;
923 })) return;
924 toxCoreSendEvent(tcmsg);
928 static extern(C) void friendMsgCB (Tox* tox, uint frnum, TOX_MESSAGE_TYPE type, const(char)* msg, usize msglen, void* udata) nothrow {
929 if (toxCoreSendEvent is null) return;
930 ToxEventBase tcmsg;
931 if (!tox.doWithLockedTP(delegate (TPInfo ti) {
932 PubKey self; tox_self_get_public_key(tox, self.ptr);
933 PubKey who; if (!tox_friend_get_public_key(tox, frnum, who.ptr)) return; // wtf?!
934 conwriteln("TOX(", ti.nick, "): friend #", frnum, " sent a message.");
936 tcmsg = new ToxEventMessage(self, who, (type == TOX_MESSAGE_TYPE_ACTION), msg[0..msglen].idup);
937 })) return;
938 toxCoreSendEvent(tcmsg);
942 static extern(C) void friendReceiptCB (Tox* tox, uint frnum, uint msgid, void* udata) nothrow {
943 if (toxCoreSendEvent is null) return;
944 ToxEventBase tcmsg;
945 if (!tox.doWithLockedTP(delegate (TPInfo ti) {
946 PubKey self; tox_self_get_public_key(tox, self.ptr);
947 PubKey who; if (!tox_friend_get_public_key(tox, frnum, who.ptr)) return; // wtf?!
948 conwriteln("TOX(", ti.nick, "): friend #", frnum, " acked message #", msgid);
950 tcmsg = new ToxEventMessageAck(self, who, (cast(long)msgid)+1);
951 })) return;
952 toxCoreSendEvent(tcmsg);
956 static extern(C) void fileRecvCB (Tox* tox, uint frnum, uint flnum, TOX_FILE_KIND kind, ulong flsize, const(char)* name, usize namelen, void* udata) nothrow {
957 if (toxCoreSendEvent is null) return;
958 if (!tox.doWithLockedTP(delegate (TPInfo ti) {
959 //PubKey self; tox_self_get_public_key(tox, self.ptr);
960 //PubKey who; if (!tox_friend_get_public_key(tox, frnum, who.ptr)) return false; // wtf?!
962 bool found = false;
963 foreach (ref TPInfo.FileRejectEvent fre; ti.rej) if (fre.frnum == frnum && fre.flnum == flnum) { found = true; break; }
964 if (!found) ti.rej ~= TPInfo.FileRejectEvent(frnum, flnum);
965 })) return;
968 if (kind == TOX_FILE_KIND_AVATAR) {
969 if (flsize < 16 || flsize > 1024*1024) {
970 timp.addRejectEvent(frnum, flnum);
971 return;
974 timp.addRejectEvent(frnum, flnum);
976 tox_file_control(tox, rej.frnum, rej.flnum, TOX_FILE_CONTROL_CANCEL, null);
981 static extern(C) void fileRecvCtlCB (Tox* tox, uint frnum, uint flnum, TOX_FILE_CONTROL ctl, void* udata) nothrow {
983 auto timp = findTP(tox);
984 if (timp is null) return;
985 PubKey who;
986 if (!tox_friend_get_public_key(tox, frnum, who.ptr)) return false; // wtf?!
987 try {
988 conprintfln("got recv ctl for friend #%s, file #%s, ctl:%s", frnum, flnum, ctl);
990 synchronized(timp) {
991 timp.friendMessageAck(who, msgid);
994 } catch (Exception e) {
995 conprintfln("\nTOX[%s] CB EXCEPTION: %s\n\n", timp.srvalias, e.toString);
998 auto timp = *cast(Account*)&udata;
999 if (timp is null || timp.tox is null) return;
1000 try {
1001 final switch (ctl) {
1002 case TOX_FILE_CONTROL_RESUME: /*timp.resumeSendByNums(frnum, flnum);*/ break;
1003 case TOX_FILE_CONTROL_PAUSE: /*timp.pauseSendByNums(frnum, flnum);*/ break;
1004 case TOX_FILE_CONTROL_CANCEL: /*timp.abortSendByNums(frnum, flnum);*/ break;
1006 } catch (Exception e) {
1007 try {
1008 conprintfln("\nTOX[%s] CB EXCEPTION: %s\n\n", timp.srvalias, e.toString);
1009 timp.fatalError();
1010 } catch (Exception) {}
1016 static extern(C) void fileChunkReqCB (Tox* tox, uint frnum, uint flnum, ulong pos, usize len, void* udata) nothrow {
1017 if (toxCoreSendEvent is null) return;
1018 if (!tox.doWithLockedTP(delegate (TPInfo ti) {
1019 PubKey self; tox_self_get_public_key(tox, self.ptr);
1020 PubKey who; if (!tox_friend_get_public_key(tox, frnum, who.ptr)) return; // wtf?!
1022 bool found = false;
1023 foreach (ref TPInfo.FileRejectEvent fre; ti.rej) if (fre.frnum == frnum && fre.flnum == flnum) { found = true; break; }
1024 if (!found) ti.rej ~= TPInfo.FileRejectEvent(frnum, flnum);
1025 })) return;
1027 //logwritefln("got chunk req recv ctl for friend #%s, file #%s, ctl:%s", frnum, flnum, ctl);
1029 try {
1030 timp.fschunks ~= ChunkToSend(frnum, flnum, pos, len);
1031 } catch (Exception e) {
1032 try {
1033 conprintfln("\nTOX[%s] CB EXCEPTION: %s\n\n", timp.srvalias, e.toString);
1034 timp.dropNetIOConnection();
1035 } catch (Exception) {}
1041 // ////////////////////////////////////////////////////////////////////////// //
1042 void bootstrap(string mode="any") (TPInfo ti) nothrow if (mode == "udp" || mode == "tcp" || mode == "any") {
1043 if (ti is null || ti.tox is null) return;
1045 ToxBootstrapServer[] loadBootNodes () nothrow {
1046 import std.path;
1047 auto bfname = buildPath(ti.toxDataDiskName.dirName, "tox_bootstrap.rc");
1048 ToxBootstrapServer[] bootnodes = null;
1049 try {
1050 bootnodes.txtunser(VFile(bfname));
1051 if (bootnodes.length > 0) return bootnodes;
1052 } catch (Exception e) {}
1053 // try to download
1054 try {
1055 bootnodes = tox_download_bootstrap_list();
1056 if (bootnodes.length > 0) {
1057 try {
1058 bootnodes.txtser(VFile(bfname, "w"));
1059 } catch (Exception e) {}
1061 return bootnodes;
1062 } catch (Exception e) {}
1063 return null;
1066 conprintfln("Tox: loading bootstrap nodes...");
1067 auto nodes = loadBootNodes();
1068 conprintfln("Tox: %s nodes loaded", nodes.length);
1069 if (nodes.length == 0) return;
1070 foreach (const ref ToxBootstrapServer srv; nodes) {
1071 if (srv.ipv4.length < 2) continue;
1072 assert(srv.ipv4[$-1] == 0);
1073 //conprintfln(" node ip: %s:%u (maintainer: %s)", srv.ipv4[0..$-1], srv.port, srv.maintainer);
1074 static if (mode == "udp") {
1075 if (srv.udp) {
1076 tox_bootstrap(ti.tox, srv.ipv4.ptr, srv.port, srv.pubkey.ptr, null);
1078 } else static if (mode == "tcp") {
1079 if (srv.tcp) {
1080 foreach (immutable ushort port; srv.tcpports) {
1081 tox_add_tcp_relay(ti.tox, srv.ipv4.ptr, port, srv.pubkey.ptr, null);
1084 } else {
1085 if (srv.udp) {
1086 tox_bootstrap(ti.tox, srv.ipv4.ptr, srv.port, srv.pubkey.ptr, null);
1087 } else if (srv.udp) {
1088 foreach (immutable ushort port; srv.tcpports) {
1089 tox_add_tcp_relay(ti.tox, srv.ipv4.ptr, port, srv.pubkey.ptr, null);
1093 tox_iterate(ti.tox, null);
1095 //conprintfln("Tox[%s]: %s nodes added", srvalias, nodes.length);
1099 // ////////////////////////////////////////////////////////////////////////// //
1100 // returns `null` if there is no such file or file cannot be loaded
1101 ubyte[] loadToxCoreData (const(char)[] toxdatafname) nothrow {
1102 import core.stdc.stdio : FILE, fopen, fclose, rename, fread, ferror, fseek, ftell, SEEK_SET, SEEK_END;
1103 import core.stdc.stdlib : malloc, free;
1104 import core.sys.posix.unistd : unlink;
1106 if (toxdatafname.length == 0) return null;
1108 static char* namebuf = null;
1109 static uint nbsize = 0;
1111 if (nbsize < toxdatafname.length+1024) {
1112 import core.stdc.stdlib : realloc;
1113 nbsize = cast(uint)toxdatafname.length+1024;
1114 namebuf = cast(char*)realloc(namebuf, nbsize);
1115 if (namebuf is null) assert(0, "out of memory");
1118 auto origName = expandTilde(namebuf[0..nbsize-6], toxdatafname);
1119 if (origName == null) return null; // oops
1120 origName.ptr[origName.length] = 0; // zero-terminate
1122 FILE* fi = fopen(namebuf, "r");
1123 if (fi is null) return null;
1124 scope(exit) fclose(fi);
1126 for (;;) {
1127 if (fseek(fi, 0, SEEK_END) == -1) {
1128 import core.stdc.errno;
1129 if (errno == EINTR) continue;
1130 return null;
1132 break;
1135 long fsize;
1136 for (;;) {
1137 fsize = ftell(fi);
1138 if (fsize == -1) {
1139 import core.stdc.errno;
1140 if (errno == EINTR) continue;
1141 return null;
1143 break;
1146 if (fsize > 1024*1024*256) { conwriteln("toxcore data file too big"); return null; }
1147 if (fsize == 0) return null; // it cannot be zero-sized
1149 for (;;) {
1150 if (fseek(fi, 0, SEEK_SET) == -1) {
1151 import core.stdc.errno;
1152 if (errno == EINTR) continue;
1153 return null;
1155 break;
1158 auto res = new ubyte[](cast(int)fsize);
1159 conwriteln("loading toxcore data; size=", fsize);
1161 auto left = res;
1162 while (left.length > 0) {
1163 auto rd = fread(left.ptr, 1, left.length, fi);
1164 if (rd == 0) { delete res; return null; } // error
1165 if (rd < cast(int)left.length) {
1166 if (!ferror(fi)) { delete res; return null; } // error
1167 import core.stdc.errno;
1168 if (errno != EINTR) { delete res; return null; } // error
1169 if (rd > 0) left = left[rd..$];
1170 continue;
1172 if (rd > left.length) { delete res; return null; } // error
1173 left = left[rd..$];
1176 if (left.length) { delete res; return null; } // error
1178 return res;
1182 bool saveToxCoreData (ToxP tox, const(char)[] toxdatafname) nothrow {
1183 import core.stdc.stdio : FILE, fopen, fclose, rename, fwrite;
1184 import core.stdc.stdlib : malloc, free;
1185 import core.sys.posix.unistd : unlink;
1186 //import std.internal.cstring;
1188 if (tox is null || toxdatafname.length == 0) return false;
1190 auto size = tox_get_savedata_size(tox);
1191 if (size > int.max/8) return false; //throw new Exception("save data too big");
1193 char* savedata = cast(char*)malloc(size);
1194 if (savedata is null) return false;
1195 scope(exit) if (savedata !is null) free(savedata);
1197 tox_get_savedata(tox, savedata);
1198 conwriteln("save toxcore data; size=", size);
1200 static char* namebuf = null;
1201 static char* namebuf1 = null;
1202 static uint nbsize = 0;
1204 if (nbsize < toxdatafname.length+1024) {
1205 import core.stdc.stdlib : realloc;
1206 nbsize = cast(uint)toxdatafname.length+1024;
1207 namebuf = cast(char*)realloc(namebuf, nbsize);
1208 if (namebuf is null) assert(0, "out of memory");
1209 namebuf1 = cast(char*)realloc(namebuf1, nbsize);
1210 if (namebuf1 is null) assert(0, "out of memory");
1213 auto origName = expandTilde(namebuf[0..nbsize-6], toxdatafname);
1214 if (origName == null) return false; // oops
1215 origName.ptr[origName.length] = 0; // zero-terminate
1217 // create temporary name
1218 namebuf1[0..origName.length] = origName[];
1219 namebuf1[origName.length..origName.length+5] = ".$$$\x00";
1221 FILE* fo = fopen(namebuf1, "w");
1222 if (fo is null) return false;
1224 auto left = savedata[0..size];
1225 while (left.length > 0) {
1226 auto wr = fwrite(left.ptr, 1, left.length, fo);
1227 if (wr < cast(int)left.length) {
1228 import core.stdc.errno;
1229 if (errno == EINTR) {
1230 if (wr > 0) left = left[wr..$];
1231 continue;
1233 break;
1235 if (wr > left.length) break; // wtf?!
1236 left = left[wr..$];
1239 // error?
1240 if (left.length) {
1241 fclose(fo);
1242 unlink(namebuf1);
1243 return false;
1245 syncFile(fo);
1246 fclose(fo);
1248 if (rename(namebuf1, namebuf) != 0) {
1249 unlink(namebuf1);
1250 return false;
1253 return true;
1257 version(ToxCoreDebug) {
1258 static extern(C) void toxCoreLogCB (Tox* tox, TOX_LOG_LEVEL level, const(char)* file, uint line, const(char)* func, const(char)* message, void* user_data) nothrow {
1259 static inout(char)[] sz (inout(char)* s) nothrow @trusted @nogc {
1260 if (s is null) return null;
1261 uint idx = 0;
1262 while (s[idx]) ++idx;
1263 return cast(inout(char)[])(s[0..idx]);
1266 conwriteln("level=", level, "; file=", sz(file), ":", line, "; func=", sz(func), "; msg=", sz(message));
1271 // ////////////////////////////////////////////////////////////////////////// //
1272 // returns `false` if can't open/create
1273 ToxP toxCreateInstance (const(char)[] toxdatafname, in ref ProtoOptions protoOpts, bool allowNew, bool* wasCreated=null) nothrow {
1274 if (wasCreated !is null) *wasCreated = false;
1276 if (toxdatafname.length == 0) return null;
1278 // create toxcore
1279 char* toxProxyHost = null;
1280 scope(exit) {
1281 import core.stdc.stdlib : free;
1282 if (toxProxyHost !is null) free(toxProxyHost);
1284 ubyte[] savedata;
1285 scope(exit) delete savedata;
1287 auto toxOpts = tox_options_new();
1288 assert(toxOpts !is null);
1289 scope(exit) if (toxOpts !is null) tox_options_free(toxOpts);
1290 tox_options_default(toxOpts);
1292 bool createNewToxCoreAccount = false;
1293 toxOpts.tox_options_set_ipv6_enabled(protoOpts.ipv6);
1294 toxOpts.tox_options_set_udp_enabled(protoOpts.udp);
1295 toxOpts.tox_options_set_local_discovery_enabled(protoOpts.localDiscovery);
1296 toxOpts.tox_options_set_hole_punching_enabled(protoOpts.holePunching);
1297 toxOpts.tox_options_set_start_port(protoOpts.startPort);
1298 toxOpts.tox_options_set_end_port(protoOpts.endPort);
1299 toxOpts.tox_options_set_tcp_port(protoOpts.tcpPort);
1301 toxOpts.tox_options_set_proxy_type(protoOpts.proxyType);
1302 if (protoOpts.proxyType != TOX_PROXY_TYPE_NONE) {
1303 import core.stdc.stdlib : malloc;
1304 toxOpts.tox_options_set_proxy_port(protoOpts.proxyPort);
1305 // create proxy address string
1306 toxProxyHost = cast(char*)malloc(protoOpts.proxyAddr.length+1);
1307 if (toxProxyHost is null) assert(0, "out of memory");
1308 toxProxyHost[0..protoOpts.proxyAddr.length] = protoOpts.proxyAddr[];
1309 toxProxyHost[protoOpts.proxyAddr.length] = 0;
1310 toxOpts.tox_options_set_proxy_host(toxProxyHost);
1313 savedata = loadToxCoreData(toxdatafname);
1314 if (savedata is null) {
1315 // create new tox instance
1316 if (wasCreated !is null) *wasCreated = true;
1317 if (!allowNew) return null;
1318 toxOpts.tox_options_set_savedata_type(TOX_SAVEDATA_TYPE_NONE);
1319 conwriteln("creating new ToxCore account...");
1320 } else {
1321 // load data
1322 conwriteln("setting ToxCore account data (", savedata.length, " bytes)");
1323 toxOpts.tox_options_set_savedata_type(TOX_SAVEDATA_TYPE_TOX_SAVE);
1324 toxOpts.tox_options_set_savedata_length(savedata.length);
1325 toxOpts.tox_options_set_savedata_data(savedata.ptr, savedata.length);
1327 scope(exit) delete savedata;
1329 version(ToxCoreDebug) {
1330 toxOpts.tox_options_set_log_callback(&toxCoreLogCB);
1333 // create tox instance
1334 TOX_ERR_NEW error;
1335 ToxP tox = tox_new(toxOpts, &error);
1336 if (tox is null) {
1337 conwriteln("cannot create ToxCore instance: error is ", error);
1338 return null;
1342 if (createNewToxCoreAccount) {
1343 // set user name
1344 if (info.nick.length > 0) {
1345 auto nc = info.nick;
1346 if (nc.length > TOX_MAX_NAME_LENGTH) nc = nc[0..TOX_MAX_NAME_LENGTH];
1347 tox_self_set_name(tox, nc.ptr, nc.length);
1349 saveToxCoreData();
1350 } else {
1351 // get user name
1352 auto nsz = tox_self_get_name_size(tox);
1353 if (nsz != 0) {
1354 if (nsz > TOX_MAX_NAME_LENGTH) nsz = TOX_MAX_NAME_LENGTH;
1355 char[TOX_MAX_NAME_LENGTH] xbuf = 0;
1356 tox_self_get_name(tox, xbuf.ptr);
1358 foreach (immutable idx, char ch; xbuf[]) {
1359 if (ch == 0) { info.nick = xbuf[0..idx].idup; break; }
1362 info.nick = xbuf[0..nsz].idup;
1367 tox_callback_self_connection_status(tox, &connectionCB);
1369 tox_callback_friend_name(tox, &friendNameCB);
1370 tox_callback_friend_status_message(tox, &friendStatusMessageCB);
1371 tox_callback_friend_status(tox, &friendStatusCB);
1372 tox_callback_friend_connection_status(tox, &friendConnectionCB);
1373 //tox_callback_friend_typing(tox, &friendTypingCB);
1374 tox_callback_friend_read_receipt(tox, &friendReceiptCB);
1375 tox_callback_friend_request(tox, &friendReqCB);
1376 tox_callback_friend_message(tox, &friendMsgCB);
1378 tox_callback_file_recv_control(tox, &fileRecvCtlCB);
1379 tox_callback_file_chunk_request(tox, &fileChunkReqCB);
1380 tox_callback_file_recv(tox, &fileRecvCB);
1381 //tox_callback_file_recv_chunk
1383 //tox_callback_conference_invite
1384 //tox_callback_conference_message
1385 //tox_callback_conference_title
1386 //tox_callback_conference_namelist_change
1388 //tox_callback_friend_lossy_packet
1389 //tox_callback_friend_lossless_packet
1391 return tox;