syndicate_actor_tox/src/syndicate_actor_tox.nim

196 lines
6.8 KiB
Nim

# SPDX-FileCopyrightText: ☭ 2022 Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, logging, parseopt, strutils, tables]
from std/sequtils import toSeq
from std/times import inMilliseconds
import preserves, syndicate, syndicate/actors
import toxcore
import ./protocol
# import ./logging
addHandler(newConsoleLogger(useStderr = true))
# register global logger to stderr
proc logging_callback(
core: Core; level: Log_Level;
file: cstring; line: uint32;
`func`: cstring; message: cstring;
user_data: pointer) {.exportc, cdecl.} =
let lvl = case level
of TOX_LOG_LEVEL_TRACE: lvlDebug
of TOX_LOG_LEVEL_DEBUG: lvlDebug
of TOX_LOG_LEVEL_INFO: lvlInfo
of TOX_LOG_LEVEL_WARNING: lvlWarn
of TOX_LOG_LEVEL_ERROR: lvlError
if lvl != lvlNone:
log(lvl, `func`, ": ", message)
type
Entity = ref object of RootObj
facet: Facet
ds: Ref
FriendHandles = object
name, statusMessage, lastOnline, typing: Handle
FriendEntity {.final.} = ref object of Entity
handles: FriendHandles
CoreHandles = object
address, name, statusMessage, connectionStatus: Handle
CoreEntity {.final.} = ref object of Entity
core: Tox
statusCounts: array[3, int]
handles: CoreHandles
friends: Table[Friend, FriendEntity]
proc init(e: Entity; turn: var Turn; parent: Ref): Handle =
assert e.facet.isNil
e.facet = turn.facet
e.ds = newDataspace(turn)
proc initCore(entity: CoreEntity; turn: var Turn; parentRef: Ref) =
assert entity.core.isNil
block: # Tox initialization
var proxy_host: cstring
entity.core = newTox do (opts: toxcore.Options):
opts.log_callback = logging_callback
debug "parsing command-line options…"
proc parseBoolParam(key, val: string): bool =
if val == "": result = true
else:
try: result = parsebool(val)
except:
quit("failed to parse " & key & " as boolean: " & val)
proc parsePortParam(key, val: string): uint16 =
try: result = uint16 parsebool(val)
except:
quit("failed to parse " & key & " as port: " & val)
for kind, key, val in getopt():
case kind
of cmdLongOption:
case key
of "ipv6":
opts.ipv6_enabled = parseBoolParam(key, val)
of "udp":
opts.udp_enabled = parseBoolParam(key, val)
of "local-discovery":
opts.local_discovery_enabled = parseBoolParam(key, val)
of "proxy":
case val
of "none": opts.proxy_type = TOX_PROXY_TYPE_NONE
of "http": opts.proxy_type = TOX_PROXY_TYPE_HTTP
of "socks5": opts.proxy_type = TOX_PROXY_TYPE_SOCKS5
else:
quit("unhandled proxy type: " & val)
of "proxy-host":
proxy_host = val
opts.proxy_host = proxy_host
of "proxy-port":
opts.proxy_port = parsePortParam(key, val)
of "start-port":
opts.start_port = parsePortParam(key, val)
of "end-port":
opts.end_port = parsePortParam(key, val)
of "tcp-port":
opts.tcp_port = parsePortParam(key, val)
of "hole-punching":
opts.hole_punching_enabled = parseBoolParam(key, val)
of "save-file": discard
else:
quit("unhandled command-line parameter: " & key)
of cmdShortOption, cmdArgument:
quit("unhandled command-line parameter: " & key)
of cmdEnd: discard
block: # Syndicate entity initialization
discard init(entity, turn, parentRef)
discard publish(turn, parentRef, ToxDataspace[Ref](
publicKey: entity.core.publicKey.bytes.toSeq,
entity: entity.ds.embed))
discard publish(turn, entity.ds,
CoreVersion(
major: int version_major(),
minor: int version_minor(),
patch: int version_patch()))
entity.handles.address = publish(turn, entity.ds,
protocol.Address(text: $entity.core.address))
entity.handles.name = publish(turn, entity.ds,
Name(name: entity.core.name))
entity.handles.statusMessage = publish(turn, entity.ds,
StatusMessage(msg: entity.core.statusMessage))
block: # Friends initialization
proc createFriend(turn: var Turn; fn: Friend) =
var fe = new FriendEntity
discard init(fe, turn, entity.ds)
discard publish(turn, entity.ds, FriendDataspace[Ref](
publicKey: entity.core.publicKey(fn).bytes.toSeq,
entity: fe.ds.embed))
fe.handles.name = publish(turn, fe.ds, Name(name: entity.core.name(fn)))
entity.friends[fn] = fe
for fn in entity.core.friends: createFriend(turn, fn)
entity.core.onSelfConnectionStatus do (status: toxcore.Connection):
run(entity.facet) do (turn: var Turn):
let conn = case status
of TOX_CONNECTION_NONE: protocol.Connection.none
of TOX_CONNECTION_TCP: protocol.Connection.tcp
of TOX_CONNECTION_UDP: protocol.Connection.udp
replace(turn, entity.ds, entity.handles.connectionStatus,
Status(status: conn))
template update[T](fe: FriendEntity; h: var Handle; a: T) =
run(fe.facet) do (turn: var Turn): replace(turn, fe.ds, h, a)
entity.core.onFriendName do (num: Friend; name: string):
let fe = entity.friends[num]
update(fe, fe.handles.name, Name(name: name))
entity.core.onFriendStatusMessage do (num: Friend; msg: string):
let fe = entity.friends[num]
update(fe, fe.handles.statusMessage, StatusMessage(msg: msg))
entity.core.onFriendTyping do (num: Friend; typing: bool):
let fe = entity.friends[num]
if typing:
update(fe, fe.handles.typing, Typing())
else:
run(fe.facet) do (turn: var Turn):
retract(turn, fe.handles.typing)
entity.core.onFriendRequest do (pk: PublicKey; msg: string):
run(entity.facet) do (turn: var Turn):
let reqHandle = publish(turn, entity.ds,
FriendRequest(key: pk.bytes.toSeq, msg: msg))
onPublish(turn, entity.ds, ?FriendAccept(key: pk.bytes.toSeq)) do:
createFriend(turn, entity.core.addFriendNoRequest(pk))
retract(turn, reqHandle)
# TODO: stop watching for the accept assertion
var alive: bool
setControlCHook do:
if not alive: quit()
alive = false
proc run(entity: CoreEntity) =
alive = true
bootDataspace("main") do (ds: Ref; turn: var Turn):
connectStdio(ds, turn)
initCore(entity, turn, ds)
onPublish(turn, ds, ?BootstrapNode) do (key: string; host: string; port: int):
info "Bootstrapping from ", key, "@", host, ":", port
try: entity.core.bootstrap(host, key.toPublicKey, uint16 port)
except ToxError as e:
error "failed to bootstrap: ", e.msg
poll()
while alive:
iterate entity.core
poll(entity.core.iterationInterval.inMilliseconds.int)
run(new CoreEntity)