shlist

share and manage lists between multiple people
Log | Files | Refs

commit fb548ecf3854d4f0aafa6e401508fd1aa30befd4
parent bfb1dc24a53e883fcc681c768633611ea198f62e
Author: kyle <kyle@0x30.net>
Date:   Wed, 10 Feb 2016 19:16:36 -0700

sl: implement core push notification/cloud messaging

- modify message handlers to pass back both regular network data and a
  notification payload
  - the notification payload gets sent directly to apnd and gcmd
- implement notifications for list_add and list_update message types
  - list_add simply messages all your mutual friends
  - list_update messages all list members + all mutual friends
- hoist device id checking into main loop
  - every message type except for device_add does this
  - so stop duplicating work and do it in one place
- new dependency on IO::Socket::UNIX

Diffstat:
Mserver/sl | 236+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
1 file changed, 147 insertions(+), 89 deletions(-)

diff --git a/server/sl b/server/sl @@ -8,6 +8,7 @@ use DBI; use File::Temp; use Getopt::Std; use IO::Socket::SSL; +use IO::Socket::UNIX; use JSON::XS; use Scalar::Util qw(looks_like_number); use Try::Tiny; @@ -77,26 +78,74 @@ while (my $client_socket = $server_socket->accept()) { $db->prepare_stmt_handles(); while (1) { + # Database errors and socket connect errors both modify this + # value, make sure it's reset + undef $@; + my ($ver, $msg_type, $request) = recv_msg($client_socket); - $log->set_msg($msg_str[$msg_type]); $db->{dbh}->begin_work; - my $response = $msg_func[$msg_type]->($db, $request); + + my $device = undef; + if ($msg_type != $msg_num{device_add}) { + (my $err, $device) = get_device($db, $request); + + if ($err) { + send_msg($client_socket, $ver, $msg_type, make_error($err)); + $db->{dbh}->commit; + next; + } + } + + my ($response, $notify) = $msg_func[$msg_type]->($db, $request, $device); $db->{dbh}->commit; if ($@) { + $log->print("db transaction aborted: $@\n"); # now rollback to undo the incomplete changes # but do it in an eval{} as it may also fail eval { $db->{dbh}->rollback }; - $log->print("db transaction aborted: $@\n"); $response->{status} = 'err'; - $response->{reason} = "database transaction aborted"; + $response->{reason} = 'database transaction aborted'; + + send_msg($client_socket, $ver, $msg_type, $response); + next; } - $log->set_msg(''); + # Reply to client send_msg($client_socket, $ver, $msg_type, $response); + + # Some messages don't send notifications + next if (! defined $notify); + + # Don't send notifications when there was en error + next if ($response->{status} eq 'err'); + + # Encode the notification message to find its size + my $msg = encode_json($notify); + my $msg_len = length($msg); + + # Try to send to various messaging daemons + send_unix("../apnd.socket", $msg, $msg_len) unless ($args{t}); + send_unix("../gcmd.socket", $msg, $msg_len) unless ($args{t}); + send_unix("testd.socket", $msg, $msg_len) if ($args{t}); + } +} + +sub send_unix { + my ($socket_path, $msg, $msg_len) = @_; + + my $socket = IO::Socket::UNIX->new( + Type => SOCK_STREAM(), + Peer => $socket_path + ); + unless ($socket) { + $log->print("$socket_path: connect failed: $!\n"); + return; } + + send_all($socket, $msg, $msg_len); } sub recv_msg { @@ -109,15 +158,17 @@ sub recv_msg { $log->print("error: unsupported protocol version $version\n"); exit 0; } - elsif ($msg_type >= @msg_str) { + + if ($msg_type >= @msg_str) { $log->print("error: unknown message type $msg_type\n"); exit 0; } - elsif ($payload_size > 4096 || $payload_size == 0) { + $log->set_msg($msg_str[$msg_type]); + + if ($payload_size > 4096 || $payload_size == 0) { $log->print("error: $payload_size byte payload invalid\n"); exit 0; } - my $payload = read_all($sock, $payload_size); try { @@ -174,6 +225,9 @@ sub send_msg { send_all($sock, pack("nnn", $ver, $msg_type, $payload_len), $header_len); send_all($sock, $payload, $payload_len); + # Clear logging prefix as late as possible so send errors above will + # include the message type in them + $log->set_msg(''); return $header_len + $payload_len; } @@ -199,11 +253,11 @@ sub send_all { sub msg_device_add { my ($db, $request) = @_; - my ($err, $ph_num, $os) = unpack_request($db, $request, 'phone_number', 'os'); - return make_error($err) if ($err); + my $ph_num = $request->{'phone_number'}; + my $os = $request->{'os'}; unless (looks_like_number($ph_num)) { - $log->print("phone number '$ph_num' invalid\n"); + $log->print("phone number invalid\n"); return make_error("the sent phone number is not a number"); } @@ -230,27 +284,23 @@ sub msg_device_add { $db->{new_device}->execute($device_id, $ph_num, $os, undef, time, time); $log->print("success, '$ph_num':'$fp' os '$os'\n"); - return make_ok( { device_id => $device_id } ); + return (make_ok( { device_id => $device_id } ), undef); } sub msg_device_update { - my ($db, $request) = @_; - - my ($err, $dev) = unpack_request($db, $request, 'device_id'); - return make_error($err) if ($err); + my ($db, $request, $dev) = @_; my $hex_token = $request->{pushtoken_hex}; $db->{update_device}->execute($hex_token, $dev->{num}); - $log->print("push token = '$hex_token'\n"); + # $log->print("push token = '$hex_token'\n"); return make_ok(); } sub msg_list_add { - my ($db, $request) = @_; + my ($db, $request, $dev) = @_; - my ($err, $dev, $list) = unpack_request($db, $request, 'device_id', 'list'); - return make_error($err) if ($err); + my $list = $request->{list}; # XXX: check that $list contains the necessary keys! @@ -263,9 +313,6 @@ sub msg_list_add { $db->{new_list_member}->execute($list_num, $dev->{num}, $now); - # XXX: send push notifications to all my mutual friends to - # update their 'other lists' section - my $resp_list = { num => $list_num, name => $list->{name}, @@ -275,19 +322,33 @@ sub msg_list_add { members => [ $dev->{phnum} ], num_members => 1 }; + my $response = make_ok( { list => $resp_list } ); $log->print("new list number is '$list_num'\n"); - return make_ok( { list => $resp_list } ); + # For push notifications a list add on your part means all your friends + # gain a list in their other lists section. Create the same response + # that lists_get_other gives back for the notify payload + $db->{mutual_friend_notify_select}->execute($dev->{num}); + my $notify->{devices} = $db->{mutual_friend_notify_select}->fetchall_arrayref(); + + $notify->{msg_type} = 'friend_added_list'; + $notify->{payload} = { + num => $resp_list->{num}, + name => $list->{name}, + members => [ $dev->{phnum} ], + num_members => 1 + }; + + return ($response, $notify); } sub msg_list_update { - my ($db, $request) = @_; + my ($db, $request, $dev) = @_; - my ($err, $dev, $list) = unpack_request($db, $request, 'device_id', 'list'); - return make_error($err) if ($err); + my $list = $request->{'list'}; - ($err) = list_number_valid($db, $list->{num}); + my ($err) = list_number_valid($db, $list->{num}); return make_error($err) if ($err); # Check that the device is in the list it wants to update @@ -297,24 +358,36 @@ sub msg_list_update { return make_error("client tried to update a list it was not in"); } + # Notify all of my mutual friends that my list changed + $db->{mutual_friend_notify_select}->execute($dev->{num}); + my $mutual_friends = $db->{mutual_friend_notify_select}->fetchall_arrayref(); + + # Notify all of the other list members that this list changed + $db->{select_list_members}->execute($list->{num}, $dev->{num}); + my $list_members = $db->{select_list_members}->fetchall_arrayref(); + + my $notify; + $notify->{devices} = [@{ $mutual_friends }, @{ $list_members }]; + + $notify->{msg_type} = 'updated_list'; + $notify->{payload} = { + num => $list->{num}, + name => $list->{name}, + date => $list->{date} + }; + # print Dumper($notify); + # Update list row, note that some values here can be optional $db->{update_list}->execute($list->{name}, $list->{date}, time, $list->{num}); $log->print("num = '$list->{num}'\n"); $log->print("name = '$list->{name}'\n") if (exists $list->{name}); $log->print("date = $list->{date}\n") if (exists $list->{date}); - # XXX: send push notifications to all my mutual friends to update their - # 'other lists' section, also tell all list members to update their - # 'lists' section - - return make_ok(); + return (make_ok(), $notify); } sub msg_list_item_add { - my ($db, $request) = @_; - - my ($err, $device) = unpack_request($db, $request, 'device_id'); - return make_error($err) if ($err); + my ($db, $request, $device) = @_; return make_error("unimplemented"); @@ -333,10 +406,8 @@ sub msg_list_item_add { } sub msg_list_join { - my ($db, $request) = @_; - - my ($err, $dev, $list_num) = unpack_request($db, $request, 'device_id', 'list_num'); - return make_error($err) if ($err); + my ($db, $request, $dev) = @_; + my $list_num = $request->{list_num}; my ($list_err, $list_num_num, $list_name, $list_date) = list_number_valid($db, $list_num); return make_error($list_err) if ($list_err); @@ -374,12 +445,10 @@ sub msg_list_join { } sub msg_list_leave { - my ($db, $request) = @_; + my ($db, $request, $dev) = @_; + my $list_num = $request->{list_num}; - my ($err, $dev, $list_num) = unpack_request($db, $request, 'device_id', 'list_num'); - return make_error($err) if ($err); - - ($err) = list_number_valid($db, $list_num); + my ($err) = list_number_valid($db, $list_num); return make_error($err) if ($err); $db->{check_list_member}->execute($list_num, $dev->{num}); @@ -417,10 +486,8 @@ sub msg_list_leave { } sub msg_friend_add { - my ($db, $request) = @_; - - my ($err, $dev, $friend_phnum) = unpack_request($db, $request, 'device_id', 'friend_phnum'); - return make_error($err) if ($err); + my ($db, $request, $dev) = @_; + my $friend_phnum = $request->{'friend_phnum'}; $log->print("'$dev->{fp}' adding '$friend_phnum'\n"); @@ -451,8 +518,7 @@ sub msg_friend_add { if ($db->{friends_select}->fetchrow_array()) { $log->print("found mutual friendship\n"); - # Adding both is technically not necessary but makes - # lookups easier + # Adding both is not necessary but makes lookups easier $db->{mutual_friend_insert}->execute($dev->{num}, $friend_num); $db->{mutual_friend_insert}->execute($friend_num, $dev->{num}); } @@ -462,10 +528,8 @@ sub msg_friend_add { } sub msg_friend_delete { - my ($db, $request) = @_; - - my ($err, $dev, $friend_phnum) = unpack_request($db, $request, 'device_id', 'friend_phnum'); - return make_error($err) if ($err); + my ($db, $request, $dev) = @_; + my $friend_phnum = $request->{'friend_phnum'}; unless (looks_like_number($friend_phnum)) { $log->print("bad friends number '$friend_phnum'\n"); @@ -495,10 +559,7 @@ sub msg_friend_delete { } sub msg_lists_get { - my ($db, $request) = @_; - - my ($err, $dev) = unpack_request($db, $request, 'device_id'); - return make_error($err) if ($err); + my ($db, $request, $dev) = @_; $log->print("gathering lists for '$dev->{fp}'\n"); @@ -533,10 +594,7 @@ sub msg_lists_get { } sub msg_lists_get_other { - my ($db, $request) = @_; - - my ($err, $dev) = unpack_request($db, $request, 'device_id'); - return make_error($err) if ($err); + my ($db, $request, $dev) = @_; $log->print("gathering lists for '$dev->{fp}'\n"); @@ -583,10 +641,9 @@ sub msg_lists_get_other { } sub msg_list_items_get { - my ($db, $request) = @_; + my ($db, $request, $dev) = @_; - my ($err, $dev, $list_id) = unpack_request($db, $request, 'device_id', 'list_num'); - return make_error($err) if ($err); + my $list_id = $request->{'list_num'}; if (!$list_id) { $log->print("received null list id"); @@ -651,30 +708,20 @@ sub make_ok { return $args; } -sub unpack_request { - # Function that gets values from a hash in a specified key order - my ($db, $request, @expected_keys) = @_; - - my @values; - for (@expected_keys) { - # Check if the caller requested a key that was not provided - if (! exists $request->{$_}) { - $log->print("bad request, missing key '$_'\n"); - return ("a missing message argument was required"); - } +# Takes a request and verifies device_id is present and valid +sub get_device { + my ($db, $request) = @_; - if ($_ ne 'device_id') { - push @values, $request->{$_}; - next; - } + unless (exists $request->{'device_id'}) { + $log->print("bad request, missing key 'device_id'\n"); + return ("a missing message argument was required"); + } - # When callers request 'device_id' we check the id is valid here - my ($err, $device) = device_id_valid($db, $request->{$_}); - return ($err) if ($err); + my ($err, $device) = device_id_valid($db, $request->{'device_id'}); + return ($err) if ($err); - push @values, $device; - } - return (undef, @values); + # All good + return (undef, $device); } sub device_id_valid { @@ -842,11 +889,17 @@ sub prepare_stmt_handles { $sql = 'insert or replace into mutual_friends (device, mutual_friend) values (?, ?)'; $self->{mutual_friend_insert} = $dbh->prepare($sql); - $sql = qq{select devices.num, devices.phone_num from devices, mutual_friends + $sql = qq{select devices.num, devices.phone_num, devices.os, devices.push_token + from devices, mutual_friends where devices.num = mutual_friends.mutual_friend and mutual_friends.device = ?}; $self->{mutual_friend_select} = $dbh->prepare($sql); + $sql = qq{select devices.os, devices.push_token from devices, mutual_friends + where devices.num = mutual_friends.mutual_friend + and mutual_friends.device = ? and devices.push_token is not null}; + $self->{mutual_friend_notify_select} = $dbh->prepare($sql); + $sql = 'delete from mutual_friends where device = ? and mutual_friend = ?'; $self->{mutual_friends_delete} = $dbh->prepare($sql); @@ -859,6 +912,11 @@ sub prepare_stmt_handles { where devices.num = list_members.device and list_members.list = ?}; $self->{list_members_phnums} = $dbh->prepare($sql); + $sql = qq{select devices.os, devices.push_token from devices, list_members + where devices.num = list_members.device and list_members.list = ? + and list_members.device != ?}; + $self->{select_list_members} = $dbh->prepare($sql); + # list_members table queries $sql = 'select device from list_members where list = ?'; $self->{get_list_members} = $dbh->prepare($sql);