Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F665086
puppet_connection.ex
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
8 KB
Subscribers
None
puppet_connection.ex
View Options
defmodule
IRC.PuppetConnection
do
require
Logger
@min_backoff
:timer
.
seconds
(
5
)
@max_backoff
:timer
.
seconds
(
2
*
60
)
@max_idle
:timer
.
hours
(
12
)
@env
Mix
.
env
defmodule
Supervisor
do
use
DynamicSupervisor
def
start_link
()
do
DynamicSupervisor
.
start_link
(
__MODULE__
,
[],
name
:
__MODULE__
)
end
def
start_child
(%
Nola.Account
{
id
:
account_id
},
%
IRC.Connection
{
id
:
connection_id
})
do
spec
=
%{
id
:
{
account_id
,
connection_id
},
start
:
{
IRC.PuppetConnection
,
:start_link
,
[
account_id
,
connection_id
]},
restart
:
:transient
}
DynamicSupervisor
.
start_child
(
__MODULE__
,
spec
)
end
@impl
true
def
init
(
_init_arg
)
do
DynamicSupervisor
.
init
(
strategy
:
:one_for_one
,
max_restarts
:
10
,
max_seconds
:
1
)
end
end
def
whereis
(
account
=
%
Nola.Account
{
id
:
account_id
},
connection
=
%
IRC.Connection
{
id
:
connection_id
})
do
{
:global
,
name
}
=
name
(
account_id
,
connection_id
)
case
:global
.
whereis_name
(
name
)
do
:undefined
->
nil
pid
->
pid
end
end
def
send_message
(
account
=
%
Nola.Account
{
id
:
account_id
},
connection
=
%
IRC.Connection
{
id
:
connection_id
},
channel
,
text
)
do
GenServer
.
cast
(
name
(
account_id
,
connection_id
),
{
:send_message
,
self
(),
channel
,
text
})
end
def
start_and_send_message
(
account
=
%
Nola.Account
{
id
:
account_id
},
connection
=
%
IRC.Connection
{
id
:
connection_id
},
channel
,
text
)
do
{
:global
,
name
}
=
name
(
account_id
,
connection_id
)
pid
=
whereis
(
account
,
connection
)
pid
=
if
!
pid
do
case
IRC.PuppetConnection.Supervisor
.
start_child
(
account
,
connection
)
do
{
:ok
,
pid
}
->
pid
{
:error
,
{
:already_started
,
pid
}}
->
pid
end
else
pid
end
GenServer
.
cast
(
pid
,
{
:send_message
,
self
(),
channel
,
text
})
end
def
start
(
account
=
%
Nola.Account
{},
connection
=
%
IRC.Connection
{})
do
IRC.PuppetConnection.Supervisor
.
start_child
(
account
,
connection
)
end
def
start_link
(
account_id
,
connection_id
)
do
GenServer
.
start_link
(
__MODULE__
,
[
account_id
,
connection_id
],
name
:
name
(
account_id
,
connection_id
))
end
def
name
(
account_id
,
connection_id
)
do
{
:global
,
{
PuppetConnection
,
account_id
,
connection_id
}}
end
def
init
([
account_id
,
connection_id
])
do
account
=
%
Nola.Account
{}
=
Nola.Account
.
get
(
account_id
)
connection
=
%
IRC.Connection
{}
=
IRC.Connection
.
lookup
(
connection_id
)
Logger
.
metadata
(
puppet_conn
:
account
.
id
<>
"@"
<>
connection
.
id
)
backoff
=
:backoff
.
init
(
@min_backoff
,
@max_backoff
)
|>
:backoff
.
type
(
:jitter
)
idle
=
:erlang
.
send_after
(
@max_idle
,
self
,
:idle
)
{
:ok
,
%{
client
:
nil
,
backoff
:
backoff
,
idle
:
idle
,
connected
:
false
,
buffer
:
[],
channels
:
[],
connection_id
:
connection_id
,
account_id
:
account_id
,
connected_server
:
nil
,
connected_port
:
nil
,
network
:
connection
.
network
},
{
:continue
,
:connect
}}
end
def
handle_continue
(
:connect
,
state
)
do
#ipv6 = if @env == :prod do
# subnet = Nola.Subnet.assign(state.account_id)
# Nola.Account.put_meta(Nola.Account.get(state.account_id), "subnet", subnet)
# ip = Pfx.host(subnet, 1)
# {:ok, ipv6} = :inet_parse.ipv6_address(to_charlist(ip))
# System.cmd("add-ip6", [ip])
# ipv6
#end
conn
=
IRC.Connection
.
lookup
(
state
.
connection_id
)
client_opts
=
[]
|>
Keyword
.
put
(
:network
,
conn
.
network
)
client
=
if
state
.
client
&&
Process
.
alive?
(
state
.
client
)
do
Logger
.
info
(
"Reconnecting client"
)
state
.
client
else
Logger
.
info
(
"Connecting"
)
{
:ok
,
client
}
=
ExIRC.Client
.
start_link
(
debug
:
false
)
ExIRC.Client
.
add_handler
(
client
,
self
())
client
end
base_opts
=
[
{
:nodelay
,
true
}
]
#{ip, opts} = case {ipv6, :inet_res.resolve(to_charlist(conn.host), :in, :aaaa)} do
# {ipv6, {:ok, {:dns_rec, _dns_header, _query, rrs = [{:dns_rr, _, _, _, _, _, _, _, _, _} | _], _, _}}} ->
# ip = rrs
# |> Enum.map(fn({:dns_rr, _, :aaaa, :in, _, _, ipv6, _, _, _}) -> ipv6 end)
# |> Enum.shuffle()
# |> List.first()
# opts = [
# :inet6,
# {:ifaddr, ipv6}
# ]
# {ip, opts}
# _ ->
{
ip
,
opts
}
=
{
to_charlist
(
conn
.
host
),
[]}
#end
conn_fun
=
if
conn
.
tls
,
do
:
:connect_ssl!
,
else
:
:connect!
apply
(
ExIRC.Client
,
conn_fun
,
[
client
,
ip
,
conn
.
port
,
base_opts
++
opts
])
{
:noreply
,
%{
state
|
client
:
client
}}
end
def
handle_continue
(
:connected
,
state
)
do
state
=
Enum
.
reduce
(
Enum
.
reverse
(
state
.
buffer
),
state
,
fn
(
b
,
state
)
->
{
:noreply
,
state
}
=
handle_cast
(
b
,
state
)
state
end
)
{
:noreply
,
%{
state
|
buffer
:
[]}}
end
def
handle_cast
(
cast
=
{
:send_message
,
_pid
,
_channel
,
_text
},
state
=
%{
connected
:
false
,
buffer
:
buffer
})
do
{
:noreply
,
%{
state
|
buffer
:
[
cast
|
buffer
]}}
end
def
handle_cast
({
:send_message
,
pid
,
channel
,
text
},
state
=
%{
connected
:
true
})
do
channels
=
if
!
Enum
.
member?
(
state
.
channels
,
channel
)
do
ExIRC.Client
.
join
(
state
.
client
,
channel
)
[
channel
|
state
.
channels
]
else
state
.
channels
end
ExIRC.Client
.
msg
(
state
.
client
,
:privmsg
,
channel
,
text
)
meta
=
%{
puppet
:
true
,
from
:
pid
}
account
=
Nola.Account
.
get
(
state
.
account_id
)
nick
=
make_nick
(
state
)
sender
=
%
ExIRC.SenderInfo
{
network
:
state
.
network
,
nick
:
suffix_nick
(
nick
),
user
:
nick
,
host
:
"puppet."
}
reply_fun
=
fn
(
text
)
->
IRC.Connection
.
broadcast_message
(
state
.
network
,
channel
,
text
)
end
message
=
%
IRC.Message
{
id
:
FlakeId
.
get
(),
at
:
NaiveDateTime
.
utc_now
(),
text
:
text
,
network
:
state
.
network
,
account
:
account
,
sender
:
sender
,
channel
:
channel
,
replyfun
:
reply_fun
,
trigger
:
IRC.Connection
.
extract_trigger
(
text
),
meta
:
meta
}
message
=
case
IRC.UserTrack
.
messaged
(
message
)
do
:ok
->
message
{
:ok
,
message
}
->
message
end
IRC.Connection
.
publish
(
message
,
[
"
#{
message
.
network
}
/
#{
channel
}
:messages"
])
idle
=
if
length
(
state
.
buffer
)
==
0
do
:erlang
.
cancel_timer
(
state
.
idle
)
:erlang
.
send_after
(
@max_idle
,
self
(),
:idle
)
else
state
.
idle
end
{
:noreply
,
%{
state
|
idle
:
idle
,
channels
:
channels
}}
end
def
handle_info
(
:idle
,
state
)
do
ExIRC.Client
.
quit
(
state
.
client
,
"Puppet was idle for too long"
)
ExIRC.Client
.
stop!
(
state
.
client
)
{
:stop
,
:normal
,
state
}
end
def
handle_info
(
:disconnected
,
state
)
do
{
delay
,
backoff
}
=
:backoff
.
fail
(
state
.
backoff
)
Logger
.
info
(
"
#{
inspect
(
self
())
}
Disconnected -- reconnecting in
#{
inspect
delay
}
ms"
)
Process
.
send_after
(
self
(),
:connect
,
delay
)
{
:noreply
,
%{
state
|
connected
:
false
,
backoff
:
backoff
}}
end
def
handle_info
(
:connect
,
state
)
do
{
:noreply
,
state
,
{
:continue
,
:connect
}}
end
# Connection successful
def
handle_info
({
:connected
,
server
,
port
},
state
)
do
Logger
.
info
(
"
#{
inspect
(
self
())
}
Connected to
#{
inspect
(
server
)
}
:
#{
port
}
#{
inspect
state
}
"
)
{
_
,
backoff
}
=
:backoff
.
succeed
(
state
.
backoff
)
base_nick
=
make_nick
(
state
)
ExIRC.Client
.
logon
(
state
.
client
,
""
,
suffix_nick
(
base_nick
),
base_nick
,
"
#{
base_nick
}
's puppet"
)
{
:noreply
,
%{
state
|
backoff
:
backoff
,
connected_server
:
server
,
connected_port
:
port
}}
end
# Logon successful
def
handle_info
(
:logged_in
,
state
)
do
Logger
.
info
(
"
#{
inspect
(
self
())
}
Logged in"
)
{
_
,
backoff
}
=
:backoff
.
succeed
(
state
.
backoff
)
# Create an UserTrack entry for the client so it's authenticated to the right account_id already.
IRC.UserTrack
.
connected
(
state
.
network
,
suffix_nick
(
make_nick
(
state
)),
make_nick
(
state
),
"puppet."
,
state
.
account_id
,
%{
puppet
:
true
})
{
:noreply
,
%{
state
|
backoff
:
backoff
}}
end
# ISUP
def
handle_info
({
:isup
,
network
},
state
)
do
{
:noreply
,
%{
state
|
network
:
network
,
connected
:
true
},
{
:continue
,
:connected
}}
end
# Been kicked
def
handle_info
({
:kicked
,
_sender
,
chan
,
_reason
},
state
)
do
{
:noreply
,
%{
state
|
channels
:
state
.
channels
--
[
chan
]}}
end
def
handle_info
(
_info
,
state
)
do
{
:noreply
,
state
}
end
def
make_nick
(
state
)
do
account
=
Nola.Account
.
get
(
state
.
account_id
)
user
=
IRC.UserTrack
.
find_by_account
(
state
.
network
,
account
)
base_nick
=
if
(
user
,
do
:
user
.
nick
,
else
:
account
.
name
)
clean_nick
=
case
String
.
split
(
base_nick
,
":"
,
parts
:
2
)
do
[
"@"
<>
nick
,
_
]
->
nick
[
nick
]
->
nick
end
clean_nick
end
if
Mix
.
env
==
:dev
do
def
suffix_nick
(
nick
),
do
:
"
#{
nick
}
[d]"
else
def
suffix_nick
(
nick
),
do
:
"
#{
nick
}
[p]"
end
end
File Metadata
Details
Attached
Mime Type
text/x-ruby
Expires
Fri, Feb 27, 9:01 AM (1 d, 9 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
87157
Default Alt Text
puppet_connection.ex (8 KB)
Attached To
rNOLA Nola
Event Timeline
Log In to Comment