record users, handling user status, new message format
parent
5a25cfcd25
commit
d6aa9f35fb
|
@ -4,13 +4,22 @@ require "json"
|
||||||
|
|
||||||
require "./common.cr"
|
require "./common.cr"
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# For now, this example only upload files.
|
||||||
|
# In a near future, we should be able to download files, too.
|
||||||
|
|
||||||
service_name = "filestorage"
|
service_name = "filestorage"
|
||||||
|
|
||||||
files_and_directories_to_transfer = Array(String).new
|
files_and_directories_to_transfer = Array(String).new
|
||||||
|
|
||||||
|
# This is the requests we will send to the server
|
||||||
|
requets = Array(Requets).new
|
||||||
|
|
||||||
|
|
||||||
OptionParser.parse do |parser|
|
OptionParser.parse do |parser|
|
||||||
parser.on "-s service-name", "--service-name service-name", "Service name." do |name|
|
parser.on "-s service-name",
|
||||||
|
"--service-name service-name",
|
||||||
|
"Service name." do |name|
|
||||||
service_name = name
|
service_name = name
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -24,19 +33,16 @@ OptionParser.parse do |parser|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
client = IPC::Client.new service_name
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Get informations about files to transfer
|
# Get informations about files to transfer
|
||||||
|
# For now, we only want to upload files, so we create an UploadRequest
|
||||||
#
|
#
|
||||||
|
|
||||||
files_info = Array(FileInfo).new
|
files_info = Array(FileInfo).new
|
||||||
|
|
||||||
puts "files and directories to transfer"
|
puts "files and directories to transfer"
|
||||||
files_and_directories_to_transfer.each do |f|
|
files_and_directories_to_transfer.each do |f|
|
||||||
puts "- #{f}"
|
|
||||||
|
|
||||||
if File.directory? f
|
if File.directory? f
|
||||||
# TODO
|
# TODO
|
||||||
puts "Directories not supported, for now"
|
puts "Directories not supported, for now"
|
||||||
|
@ -45,11 +51,11 @@ files_and_directories_to_transfer.each do |f|
|
||||||
files_info << FileInfo.new file
|
files_info << FileInfo.new file
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if File.exists? f
|
if ! File.exists? f
|
||||||
puts "#{f} does not exist"
|
puts "#{f} does not exist"
|
||||||
elsif File.file? f
|
elsif ! File.file? f
|
||||||
puts "#{f} is neither a directory or a file"
|
puts "#{f} is neither a directory or a file"
|
||||||
elsif File.readable? f
|
elsif ! File.readable? f
|
||||||
puts "#{f} is not readable"
|
puts "#{f} is not readable"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -57,10 +63,16 @@ end
|
||||||
|
|
||||||
pp! files_info
|
pp! files_info
|
||||||
|
|
||||||
exit 0
|
requests << UploadRequest.new files_info
|
||||||
|
|
||||||
#
|
#
|
||||||
# Create the authentication message, including files info
|
# Connection to the service
|
||||||
|
#
|
||||||
|
|
||||||
|
client = IPC::Client.new service_name
|
||||||
|
|
||||||
|
#
|
||||||
|
# Sending the authentication message, including files info
|
||||||
#
|
#
|
||||||
|
|
||||||
token = Token.new 1002, "karchnu"
|
token = Token.new 1002, "karchnu"
|
||||||
|
@ -69,6 +81,10 @@ authentication_message = AuthenticationMessage.new token, files_info
|
||||||
|
|
||||||
client.send(1.to_u8, authentication_message.to_json)
|
client.send(1.to_u8, authentication_message.to_json)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Receiving a response
|
||||||
|
#
|
||||||
|
|
||||||
m = client.read
|
m = client.read
|
||||||
# puts "message received: #{m.to_s}"
|
# puts "message received: #{m.to_s}"
|
||||||
# puts "message received payload: #{String.new m.payload}"
|
# puts "message received payload: #{String.new m.payload}"
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
require "uuid"
|
require "uuid"
|
||||||
|
|
||||||
|
enum MessageType
|
||||||
|
Error
|
||||||
|
AuthenticationMessage
|
||||||
|
Response
|
||||||
|
Transfer
|
||||||
|
end
|
||||||
|
|
||||||
|
# For now, upload and download are sequentials.
|
||||||
|
# In a future version, we will be able to send
|
||||||
|
# arbitrary parts of each file.
|
||||||
|
|
||||||
class Token
|
class Token
|
||||||
JSON.mapping({
|
JSON.mapping({
|
||||||
uid: Int32,
|
uid: Int32,
|
||||||
|
@ -10,28 +21,62 @@ class Token
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Who knows, maybe someday we will be on UDP, too.
|
||||||
|
#class SHA256
|
||||||
|
# JSON.mapping({
|
||||||
|
# chunk: Slice(UInt8)
|
||||||
|
# })
|
||||||
|
#end
|
||||||
|
|
||||||
|
|
||||||
|
# A file has a name, a size and tags.
|
||||||
class FileInfo
|
class FileInfo
|
||||||
JSON.mapping({
|
JSON.mapping({
|
||||||
name: String,
|
name: String,
|
||||||
size: UInt32,
|
size: UInt64,
|
||||||
|
# list of SHA256, if we are on UDP
|
||||||
|
# chunks: Array(SHA256),
|
||||||
tags: Array(String)?
|
tags: Array(String)?
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# debugging constructor
|
||||||
def initialize(@name, @size, @tags = nil)
|
def initialize(@name, @size, @tags = nil)
|
||||||
|
# If on UDP
|
||||||
|
# @chunks = Array(SHA256).new
|
||||||
|
# arbitrary values here
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(file : File, @tags = nil)
|
def initialize(file : File, @tags = nil)
|
||||||
@name = file.basename
|
@name = File.basename file.path
|
||||||
@size = file.size
|
@size = file.size
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class Request
|
||||||
|
end
|
||||||
|
|
||||||
|
class UploadRequest < Request
|
||||||
|
property files_to_upload : Array(FileInfo)
|
||||||
|
|
||||||
|
def initialize(@files_to_upload)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# WIP
|
||||||
|
class DownloadRequest < Request
|
||||||
|
property names : Array(String)?,
|
||||||
|
property tags : Array(String)?
|
||||||
|
|
||||||
|
def initialize(@names = nil, @tags = nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class AuthenticationMessage
|
class AuthenticationMessage
|
||||||
JSON.mapping({
|
JSON.mapping({
|
||||||
mid: String,
|
mid: String,
|
||||||
token: Token,
|
token: Token,
|
||||||
files: Array(FileInfo),
|
requests: Array(Requests)
|
||||||
tags: Array(String)?
|
|
||||||
})
|
})
|
||||||
|
|
||||||
def initialize(@token, @files, @tags = nil)
|
def initialize(@token, @files, @tags = nil)
|
||||||
|
@ -50,7 +95,7 @@ class Response
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Transfer
|
class TransferMessage
|
||||||
JSON.mapping({
|
JSON.mapping({
|
||||||
mid: String,
|
mid: String,
|
||||||
chunk: String,
|
chunk: String,
|
||||||
|
|
|
@ -2,13 +2,16 @@ require "json"
|
||||||
|
|
||||||
require "./common.cr"
|
require "./common.cr"
|
||||||
|
|
||||||
|
files_info = Array(FileInfo).new
|
||||||
|
files_info << FileInfo.new "file.txt", 4123.to_u64, %w(important truc machin)
|
||||||
|
|
||||||
token = Token.new 1002, "karchnu"
|
token = Token.new 1002, "karchnu"
|
||||||
authentication_message = AuthenticationMessage.new token
|
authentication_message = AuthenticationMessage.new token, files_info
|
||||||
|
|
||||||
# TODO, TEST, DEBUG, XXX, FIXME
|
# TODO, TEST, DEBUG, XXX, FIXME
|
||||||
pp! authentication_message.to_json
|
pp! authentication_message.to_json
|
||||||
|
|
||||||
|
|
||||||
am_from_json = AuthenticationMessage.from_json authentication_message.to_json
|
am_from_json = AuthenticationMessage.from_json authentication_message.to_json
|
||||||
|
|
||||||
pp! am_from_json
|
pp! am_from_json
|
||||||
|
|
83
src/main.cr
83
src/main.cr
|
@ -30,33 +30,50 @@ OptionParser.parse do |parser|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# keep track of connected users
|
# keep track of connected users and their requests
|
||||||
|
# TODO: requests should be handled concurrently
|
||||||
class User
|
class User
|
||||||
|
property uid : Int32
|
||||||
property token : Token
|
property token : Token
|
||||||
|
property requests : Array(Request)
|
||||||
|
|
||||||
def initialize(@token)
|
def initialize(@token)
|
||||||
|
@uid = token.uid
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# list of connected users
|
# list of connected users
|
||||||
# fd => User
|
# fd => uid
|
||||||
connected_users = Hash(Int32, User).new
|
connected_users = Hash(Int32, Int32).new
|
||||||
|
users_status = Hash(Int32, User).new
|
||||||
|
|
||||||
service = IPC::SwitchingService.new service_name
|
service = IPC::SwitchingService.new service_name
|
||||||
|
|
||||||
|
def receiving_files(user : User, event : IPC::Event::Message)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Could be the reception of a file or a file request
|
||||||
|
def request_handling(user : User, event : IPC::Event::Message)
|
||||||
|
puts "request handling"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Here we get requests from the message received
|
||||||
|
#
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
service.loop do |event|
|
service.loop do |event|
|
||||||
case event
|
case event
|
||||||
when IPC::Event::Timer
|
when IPC::Event::Timer
|
||||||
puts "#{CORANGE}IPC::Event::Timer#{CRESET}"
|
puts "#{CORANGE}IPC::Event::Timer#{CRESET}"
|
||||||
|
|
||||||
# puts "Disconnected client is: #{client_name}"
|
|
||||||
|
|
||||||
when IPC::Event::Connection
|
when IPC::Event::Connection
|
||||||
puts "#{CBLUE}IPC::Event::Connection: #{event.connection.fd}#{CRESET}"
|
puts "#{CBLUE}IPC::Event::Connection: #{event.connection.fd}#{CRESET}"
|
||||||
|
|
||||||
when IPC::Event::Disconnection
|
when IPC::Event::Disconnection
|
||||||
puts "#{CBLUE}IPC::Event::Disconnection: #{event.connection.fd}#{CRESET}"
|
puts "#{CBLUE}IPC::Event::Disconnection: #{event.connection.fd}#{CRESET}"
|
||||||
|
|
||||||
connected_users.select! do |fd, user|
|
connected_users.select! do |fd, uid|
|
||||||
fd != event.connection.fd
|
fd != event.connection.fd
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -72,31 +89,57 @@ service.loop do |event|
|
||||||
puts "#{CBLUE}IPC::Event::Message#{CRESET}: #{event.connection.fd}"
|
puts "#{CBLUE}IPC::Event::Message#{CRESET}: #{event.connection.fd}"
|
||||||
|
|
||||||
# 1. test if the client is already authenticated
|
# 1. test if the client is already authenticated
|
||||||
if user = connected_users[event.connection.fd]?
|
if userid = connected_users[event.connection.fd]?
|
||||||
puts "User is connected: #{user.token.login}"
|
puts "User is connected: #{user.token.login}"
|
||||||
|
request_handling users_status[userid], event
|
||||||
else
|
else
|
||||||
puts "User is not currently connected"
|
puts "User is not currently connected"
|
||||||
|
|
||||||
authentication_message = AuthenticationMessage.from_json(String.new event.message.payload)
|
# The first message sent to the server has to be the AuthenticationMessage.
|
||||||
|
# Users sent their token (JWT) to authenticate themselves.
|
||||||
|
# The token contains the user id, its login and a few other parameters.
|
||||||
|
# (see the authd documentation).
|
||||||
|
authentication_message =
|
||||||
|
AuthenticationMessage.from_json(
|
||||||
|
String.new event.message.payload
|
||||||
|
)
|
||||||
|
|
||||||
authentication_message.files.each do |file|
|
# Is the user already recorded in users_status?
|
||||||
puts "uploading #{file.name} - #{file.size} bytes"
|
if users_status[authentication_message.token.uid]?
|
||||||
end
|
puts "We already knew the user #{authentication_message.token.uid}"
|
||||||
|
pp! users_status[authentication_message.token.uid]
|
||||||
|
else
|
||||||
|
# AuthenticationMessage includes requests.
|
||||||
|
new_user =
|
||||||
|
User.new authentication_message.token,
|
||||||
|
authentication_message.requests
|
||||||
|
|
||||||
|
connected_users[event.connection.fd] = new_user.uid
|
||||||
|
|
||||||
|
# record the new user in users_status
|
||||||
|
users_status[new_user.uid] = new_user
|
||||||
|
|
||||||
new_user = User.new authentication_message.token
|
|
||||||
connected_users[event.connection.fd] = new_user
|
|
||||||
puts "New user is: #{new_user.token.login}"
|
puts "New user is: #{new_user.token.login}"
|
||||||
|
|
||||||
response = Response.new authentication_message.mid, "Ok"
|
|
||||||
event.connection.send 2.to_u8, response.to_json
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The user is now connected.
|
||||||
|
user = users_status[authentication_message.token.uid]
|
||||||
|
|
||||||
# puts "New connected client is: #{client_name}"
|
# We verify the user's rights to upload files.
|
||||||
|
# TODO RIGHTS
|
||||||
|
# if user wants to upload but not allowed to: Response
|
||||||
|
# if user wants to get a file but not allowed to: Response
|
||||||
|
|
||||||
# The first message is the connection.
|
# The user is authorized to upload files.
|
||||||
# Users sent their token (JWT) to authenticate.
|
|
||||||
# From the token, we get the user id, its login and a few other parameters (see the authd documentation).
|
# TODO: quotas
|
||||||
|
# Quotas are not defined yet.
|
||||||
|
|
||||||
|
# Sending a response.
|
||||||
|
# The response is "Ok" when the message is well received and authorized.
|
||||||
|
response = Response.new authentication_message.mid, "Ok"
|
||||||
|
event.connection.send MessageType::Response.to_u8, response.to_json
|
||||||
|
end
|
||||||
else
|
else
|
||||||
raise "Event type not supported."
|
raise "Event type not supported."
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue