filestoraged/src/server/storage.cr

151 lines
4.1 KiB
Crystal

require "json"
require "uuid"
require "uuid/json"
require "openssl"
require "dodb"
require "base64"
require "../common/utils"
require "./storage/*"
# private function
def digest(value : String)
underlying_io = IO::Memory.new value
buffer = Bytes.new(4096)
io = OpenSSL::DigestIO.new underlying_io, "SHA256"
io.read buffer
io.digest.hexstring
end
# XXX TODO FIXME: architectural questions
# Why keeping upload and download requests?
# The server can be just for uploads, delegating downloads to HTTP.
# In environment without HTTP integration, this could still be pertinent.
class FileStorage::Storage
property db : DODB::DataBase(TransferInfo)
# Search file informations by their index, owner and tags.
property db_by_filedigest : DODB::Index(TransferInfo)
property db_by_owner : DODB::Partition(TransferInfo)
property db_by_tags : DODB::Tags(TransferInfo)
def initialize(storage_directory, file_info_directory)
@db = DODB::DataBase(TransferInfo).new @file_info_directory
# Create indexes, partitions and tags objects.
@db_by_filedigest = @db.new_index "filedigest", &.file_info.digest
@db_by_owner = @db.new_partition "owner", &.owner.to_s
@db_by_tags = @db.new_tags "tags", &.file_info.tags
end
# Reception of a file chunk.
def transfer(message : FileStorage::Transfer, user : User) : FileStorage::Response
# We received a message containing a chunk of file.
mid = message.mid
mid ||= "no message id"
# Get the transfer info from the db
transfer_info = @db_by_filedigest.get message.filedigest
if transfer_info.nil?
# The user has to send an upload request before sending anything.
# If not the case, it should be discarded.
raise "file not recorded"
end
chunk_number = message.chunk.n
data = Base64.decode message.data
# TODO: verify that the chunk sent was really missing.
if transfer_info.chunks.select(chunk_number).size > 0
write_a_chunk user.uid.to_s, transfer_info.file_info, chunk_number, data
else
raise "non existent chunk or already uploaded"
end
remove_chunk_from_db transfer_info, chunk_number
# TODO: verify the digest, if no more chunks.
FileStorage::Response::Transfer.new mid
rescue e
puts "Error handling transfer: #{e.message}"
FileStorage::Response.new mid.not_nil!, "Not Ok", "Unexpected error: #{e.message}"
end
# the client sent an upload request
def upload(request : FileStorage::Request::Upload, user : User) : FileStorage::Response
mid = request.mid
mid ||= "no message id"
puts "hdl upload: mid=#{request.mid}"
pp! request
# TODO: verify the rights and quotas of the user
# file_info attributes: name, size, nb_chunks, digest, tags
# First: check if the file already exists
transfer_info = @db_by_filedigest.get? request.file.digest
if transfer_info.nil?
# In case file informations aren't already registered
# which is normal at this point
transfer_info = TransferInfo.new user.uid, request.file
@db << transfer_info
else
# File information already exists, request may be duplicated
# In this case: ignore the upload request
end
FileStorage::Response::Upload.new request.mid
rescue e
puts "Error handling transfer: #{e.message}"
FileStorage::Response.new mid.not_nil!, "Not Ok", "Unexpected error: #{e.message}"
end
# TODO
# The client sent a download request.
def download(request : FileStorage::DownloadRequest, user : User) : FileStorage::Response
puts "hdl download: mid=#{request.mid}"
pp! request
FileStorage::Response::Download.new request.mid
end
# Entry point for request management
# Each request should have a response.
# Then, responses are sent in a single message.
def requests(requests : Array(FileStorage::Request),
user : User,
event : IPC::Event::Message) : Array(FileStorage::Response)
puts "hdl request"
responses = Array(FileStorage::Response).new
requests.each do |request|
case request
when FileStorage::DownloadRequest
responses << download request, user
when FileStorage::UploadRequest
responses << upload request, user
else
raise "request not understood"
end
puts
end
responses
end
end