mirror of
https://github.com/matrix-construct/construct
synced 2024-11-16 23:10:54 +01:00
379 lines
6.6 KiB
C++
379 lines
6.6 KiB
C++
// Matrix Construct
|
|
//
|
|
// Copyright (C) Matrix Construct Developers, Authors & Contributors
|
|
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
|
|
//
|
|
// Permission to use, copy, modify, and/or distribute this software for any
|
|
// purpose with or without fee is hereby granted, provided that the above
|
|
// copyright notice and this permission notice is present in all copies. The
|
|
// full license for this software is available in the LICENSE file.
|
|
|
|
#include "media.h"
|
|
|
|
mapi::header
|
|
IRCD_MODULE
|
|
{
|
|
"11.7 :Content respository"
|
|
};
|
|
|
|
decltype(media_log)
|
|
media_log
|
|
{
|
|
"media"
|
|
};
|
|
|
|
std::set<m::room::id>
|
|
downloading;
|
|
|
|
ctx::dock
|
|
downloading_dock;
|
|
|
|
m::room::id::buf
|
|
download(const string_view &server,
|
|
const string_view &mediaid,
|
|
const net::hostport &remote)
|
|
{
|
|
const m::room::id::buf room_id
|
|
{
|
|
file_room_id(server, mediaid)
|
|
};
|
|
|
|
download(server, mediaid, remote, room_id);
|
|
return room_id;
|
|
}
|
|
|
|
m::room
|
|
download(const string_view &server,
|
|
const string_view &mediaid,
|
|
const net::hostport &remote,
|
|
const m::room::id &room_id)
|
|
try
|
|
{
|
|
auto iit
|
|
{
|
|
downloading.emplace(room_id)
|
|
};
|
|
|
|
if(!iit.second)
|
|
{
|
|
do
|
|
{
|
|
downloading_dock.wait();
|
|
}
|
|
while(downloading.count(room_id));
|
|
return room_id;
|
|
}
|
|
|
|
const unwind uw{[&iit]
|
|
{
|
|
downloading.erase(iit.first);
|
|
downloading_dock.notify_all();
|
|
}};
|
|
|
|
if(exists(m::room{room_id}))
|
|
return room_id;
|
|
|
|
const unique_buffer<mutable_buffer> buf
|
|
{
|
|
16_KiB
|
|
};
|
|
|
|
const auto pair
|
|
{
|
|
download(buf, server, mediaid, remote)
|
|
};
|
|
|
|
const auto &head
|
|
{
|
|
pair.first
|
|
};
|
|
|
|
const const_buffer &content
|
|
{
|
|
pair.second
|
|
};
|
|
|
|
char mime_type_buf[64];
|
|
const auto &content_type
|
|
{
|
|
magic::mime(mime_type_buf, content)
|
|
};
|
|
|
|
if(content_type != head.content_type) log::warning
|
|
{
|
|
media_log, "Server %s claims thumbnail %s is '%s' but we think it is '%s'",
|
|
string(remote),
|
|
mediaid,
|
|
head.content_type,
|
|
content_type
|
|
};
|
|
|
|
m::vm::opts::commit vmopts;
|
|
vmopts.history = false;
|
|
const m::room room
|
|
{
|
|
room_id, &vmopts
|
|
};
|
|
|
|
create(room, m::me.user_id, "file");
|
|
|
|
const size_t written
|
|
{
|
|
write_file(room, content, content_type)
|
|
};
|
|
|
|
return room;
|
|
}
|
|
catch(const ircd::server::unavailable &e)
|
|
{
|
|
throw http::error
|
|
{
|
|
http::BAD_GATEWAY, e.what()
|
|
};
|
|
}
|
|
|
|
std::pair<http::response::head, unique_buffer<mutable_buffer>>
|
|
download(const mutable_buffer &head_buf,
|
|
const string_view &server,
|
|
const string_view &mediaid,
|
|
net::hostport remote,
|
|
server::request::opts *const opts)
|
|
{
|
|
if(!remote)
|
|
remote = server;
|
|
|
|
window_buffer wb{head_buf};
|
|
thread_local char uri[4_KiB];
|
|
http::request
|
|
{
|
|
wb, host(remote), "GET", fmt::sprintf
|
|
{
|
|
uri, "/_matrix/media/r0/download/%s/%s", server, mediaid
|
|
}
|
|
};
|
|
|
|
const const_buffer out_head
|
|
{
|
|
wb.completed()
|
|
};
|
|
|
|
// Remaining space in buffer is used for received head
|
|
const mutable_buffer in_head
|
|
{
|
|
data(head_buf) + size(out_head), size(head_buf) - size(out_head)
|
|
};
|
|
|
|
//TODO: --- This should use the progress callback to build blocks
|
|
// Null content buffer will cause dynamic allocation internally.
|
|
const mutable_buffer in_content{};
|
|
server::request remote_request
|
|
{
|
|
remote, { out_head }, { in_head, in_content }, opts
|
|
};
|
|
|
|
//TODO: conf
|
|
if(!remote_request.wait(seconds(10), std::nothrow))
|
|
throw http::error
|
|
{
|
|
http::REQUEST_TIMEOUT
|
|
};
|
|
|
|
const auto &code
|
|
{
|
|
remote_request.get()
|
|
};
|
|
|
|
if(code != http::OK)
|
|
return {};
|
|
|
|
parse::buffer pb{remote_request.in.head};
|
|
parse::capstan pc{pb};
|
|
pc.read += size(remote_request.in.head);
|
|
return
|
|
{
|
|
http::response::head{pc}, std::move(remote_request.in.dynamic)
|
|
};
|
|
}
|
|
|
|
size_t
|
|
write_file(const m::room &room,
|
|
const const_buffer &content,
|
|
const string_view &content_type)
|
|
{
|
|
//TODO: TXN
|
|
send(room, m::me.user_id, "ircd.file.stat", "size",
|
|
{
|
|
{ "value", long(size(content)) }
|
|
});
|
|
|
|
//TODO: TXN
|
|
send(room, m::me.user_id, "ircd.file.stat", "type",
|
|
{
|
|
{ "value", content_type }
|
|
});
|
|
|
|
const auto lpath
|
|
{
|
|
fs::make_path({fs::DPATH, "media"})
|
|
};
|
|
|
|
char pathbuf[768];
|
|
size_t pathlen{0};
|
|
pathlen = strlcpy(pathbuf, lpath);
|
|
pathlen = strlcat(pathbuf, "/"_sv); //TODO: fs utils
|
|
const mutable_buffer pathpart
|
|
{
|
|
pathbuf + pathlen, sizeof(pathbuf) - pathlen
|
|
};
|
|
|
|
size_t off{0}, wrote{0};
|
|
while(off < size(content))
|
|
{
|
|
const size_t blksz
|
|
{
|
|
std::min(size(content) - off, size_t(32_KiB))
|
|
};
|
|
|
|
const const_buffer &block
|
|
{
|
|
data(content) + off, blksz
|
|
};
|
|
|
|
const sha256::buf hash_
|
|
{
|
|
sha256{block}
|
|
};
|
|
|
|
char b58buf[hash_.size() * 2];
|
|
const string_view hash
|
|
{
|
|
b58encode(b58buf, hash_)
|
|
};
|
|
|
|
send(room, m::me.user_id, "ircd.file.block",
|
|
{
|
|
{ "size", long(blksz) },
|
|
{ "hash", hash }
|
|
});
|
|
|
|
const string_view path
|
|
{
|
|
pathbuf, pathlen + copy(pathpart, hash)
|
|
};
|
|
|
|
wrote += size(fs::overwrite(path, block));
|
|
off += blksz;
|
|
}
|
|
|
|
assert(off == size(content));
|
|
assert(wrote == off);
|
|
return wrote;
|
|
}
|
|
|
|
size_t
|
|
read_each_block(const m::room &room,
|
|
const std::function<void (const const_buffer &)> &closure)
|
|
{
|
|
const auto lpath
|
|
{
|
|
fs::make_path({fs::DPATH, "media"})
|
|
};
|
|
|
|
char pathbuf[768];
|
|
size_t pathlen{0};
|
|
pathlen = strlcpy(pathbuf, lpath);
|
|
pathlen = strlcat(pathbuf, "/"_sv); //TODO: fs utils
|
|
const mutable_buffer pathpart
|
|
{
|
|
pathbuf + pathlen, sizeof(pathbuf) - pathlen
|
|
};
|
|
|
|
// Block buffer
|
|
const unique_buffer<mutable_buffer> buf
|
|
{
|
|
64_KiB
|
|
};
|
|
|
|
size_t ret{0};
|
|
m::room::messages it{room, 1};
|
|
for(; bool(it); ++it)
|
|
{
|
|
const m::event &event{*it};
|
|
if(at<"type"_>(event) != "ircd.file.block")
|
|
continue;
|
|
|
|
const auto &hash
|
|
{
|
|
unquote(at<"content"_>(event).at("hash"))
|
|
};
|
|
|
|
const auto &blksz
|
|
{
|
|
at<"content"_>(event).get<size_t>("size")
|
|
};
|
|
|
|
const string_view path
|
|
{
|
|
pathbuf, pathlen + copy(pathpart, hash)
|
|
};
|
|
|
|
const const_buffer &block
|
|
{
|
|
fs::read(path, buf)
|
|
};
|
|
|
|
if(unlikely(size(block) != blksz)) throw error
|
|
{
|
|
"File [%s] block [%s] (%s) blksz %zu != %zu",
|
|
string_view{room.room_id},
|
|
string_view{at<"event_id"_>(event)},
|
|
path,
|
|
blksz,
|
|
size(block)
|
|
};
|
|
|
|
assert(size(block) == blksz);
|
|
ret += size(block);
|
|
closure(block);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
m::room::id::buf
|
|
file_room_id(const string_view &server,
|
|
const string_view &file)
|
|
{
|
|
m::room::id::buf ret;
|
|
file_room_id(ret, server, file);
|
|
return ret;
|
|
}
|
|
|
|
m::room::id
|
|
file_room_id(m::room::id::buf &out,
|
|
const string_view &server,
|
|
const string_view &file)
|
|
{
|
|
if(empty(server) || empty(file))
|
|
throw m::BAD_REQUEST
|
|
{
|
|
"Invalid MXC: empty server or file parameters..."
|
|
};
|
|
|
|
size_t len;
|
|
thread_local char buf[512];
|
|
len = strlcpy(buf, server);
|
|
len = strlcat(buf, "/"_sv);
|
|
len = strlcat(buf, file);
|
|
const sha256::buf hash
|
|
{
|
|
sha256{string_view{buf, len}}
|
|
};
|
|
|
|
out =
|
|
{
|
|
b58encode(buf, hash), my_host()
|
|
};
|
|
|
|
return out;
|
|
}
|