Reworked file uploads to include server-generated thumbnails

This commit is contained in:
rexy712 2019-09-15 13:13:30 -07:00
parent 46d3652fa3
commit c1b387d3e7
8 changed files with 140 additions and 48 deletions

View File

@ -103,41 +103,36 @@ namespace matrix{
//NOTE alias equivalents just give the uploaded file a name other than the filename.
/*
* Upload a file as a raw file.
* Takes filename as argument.
* Takes file data as argument.
* Returns: file_info struct containing the url of the file on the homeserver.
* Note check the file_info::fileurl field to check if the upload succeeded.
*/
file_info upload_file(const raii::string_base& filename)const;
file_info upload_file(const raii::string_base& filename, const raii::string_base& alias)const;
file_info upload_file(const file_details& file)const;
/*
* Upload a file as an image.
* Note: requires FreeImagePlus to treat the file as an image, otherwise will just upload as a raw file.
* Returns: image_info struct containing the url of the image on the homeserver.
* Note check the image_info::fileurl field to check if the upload succeeded.
*/
image_info upload_image(const raii::string_base& filename)const;
image_info upload_image(const raii::string_base& filename, const raii::string_base& alias)const;
image_info upload_image(const image_details& file)const;
/*
* Upload a file as a video.
* Note: requires ffmpeg to treat the file as a video, otherwise will just upload as a raw file.
* Upload data as a video.
* Returns: video_info struct containing the url of the image on the homeserver.
* Note check the video_info::fileurl field to check if the upload succeeded.
*/
video_info upload_video(const raii::string_base& filename)const;
video_info upload_video(const raii::string_base& filename, const raii::string_base& alias)const;
video_info upload_video(const video_details& file)const;
/*
* Upload a file as an audio file.
* Note: requires ffmpeg to treat the file as a audio, otherwise will just upload as a raw file.
* Returns: audio_info struct containing the url of the image on the homeserver.
* Note check the audio_info::fileurl field to check if the upload succeeded.
*/
audio_info upload_audio(const raii::string_base& filename)const;
audio_info upload_audio(const raii::string_base& filename, const raii::string_base& alias)const;
audio_info upload_audio(const audio_details& file)const;
bool create_thumbnail(image_info& info)const;
bool create_thumbnail(video_info& video)const;
private:
raii::rjp_string _upload_file(raii::filerd& fp, const raii::curl_llist& header)const;
file_info _upload_file(const file_details& file, const raii::curl_llist& header)const;
};
}

View File

@ -52,6 +52,7 @@ namespace matrix{
const raii::string& whoami(void)const;
const raii::string& displayname(void)const;
const raii::string& profile_picture(void)const;
raii::string file_thumbnail(const raii::string_base& homeserver, const raii::string_base& fileurl, int width, int height, const raii::string_base& method)const;
raii::string logout(const raii::string_base& homeserver, const raii::string_base& access_token)const;
raii::string sync(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& next_batch, const raii::string_base& timeout)const;
raii::string power_level(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& roomid)const;

View File

@ -25,6 +25,19 @@
namespace matrix{
struct file_details{
raii::string data;
raii::string name;
};
struct image_details : public file_details{
int width, height;
raii::string mimetype;
};
struct video_details : public image_details{};
struct audio_details : public file_details{
raii::string mimetype;
};
struct file_info{
raii::rjp_string fileurl;
raii::string filename;
@ -37,6 +50,7 @@ namespace matrix{
size_t height;
raii::rjp_string thumburl;
raii::string thumbmime;
size_t thumb_width;
size_t thumb_height;
size_t thumbsize;

View File

@ -102,6 +102,7 @@ namespace raii{
void reset(char* val = nullptr);
//Stop managing stored pointer. Does not free.
char* release(void);
bool resize(size_t newsize);
//Length of string not including null terminator
constexpr size_t length(void)const{return m_length;}
@ -144,6 +145,8 @@ namespace raii{
string_intermediary(void) = default;
string_intermediary(char* data, size_t len):
string_base(data, len){}
string_intermediary(const char* data, size_t len):
string_base(reinterpret_cast<char*>(Allocator::copy(data, len+1)), len){}
string_intermediary(const char* data):
string_base(strlen(data))
{

View File

@ -88,60 +88,112 @@ namespace matrix{
}
//upload media
file_info client::upload_file(const raii::string_base& filename)const{
return upload_file(filename, raii::static_string());
file_info client::upload_file(const file_details& file)const{
return _upload_file(file, raii::curl_llist{});
}
file_info client::upload_file(const raii::string_base& filename, const raii::string_base& alias)const{
raii::filerd fd(filename);
if(!fd) return {};
return file_info{_upload_file(fd, raii::curl_llist{}), alias ? alias : filename, {}, fd.length()};
}
image_info client::upload_image(const raii::string_base& filename)const{
return upload_image(filename, raii::static_string());
}
image_info client::upload_image(const raii::string_base& filename, const raii::string_base& alias)const{
image_info client::upload_image(const image_details& file)const{
image_info ret = {};
ret = upload_file(filename, alias);
raii::curl_llist headers(raii::string("Content-Type: "_ss + file.mimetype));
ret = _upload_file(file, headers);
ret.width = file.width;
ret.height = file.height;
ret.mimetype = file.mimetype;
return ret;
}
video_info client::upload_video(const raii::string_base& filename)const{
return upload_video(filename, raii::static_string());
}
audio_info client::upload_audio(const raii::string_base& filename)const{
return upload_audio(filename, raii::static_string());
}
video_info client::upload_video(const raii::string_base& filename, const raii::string_base& alias)const{
video_info ret = {};
ret = upload_file(filename, alias);
return ret;
}
audio_info client::upload_audio(const raii::string_base& filename, const raii::string_base& alias)const{
audio_info client::upload_audio(const audio_details& file)const{
audio_info ret = {};
ret = upload_file(filename, alias);
raii::curl_llist headers(raii::string("Content-Type: "_ss + file.mimetype));
ret = _upload_file(file, headers);
ret.mimetype = file.mimetype;
return ret;
}
video_info client::upload_video(const video_details& file)const{
video_info ret = {};
raii::curl_llist headers(raii::string("Content-Type: "_ss + file.mimetype));
ret = _upload_file(file, headers);
ret.width = file.width;
ret.height = file.height;
ret.mimetype = file.mimetype;
return ret;
}
static size_t _thumbnail_header_callback(char* ptr, size_t size, size_t nmemb, void* userdata){
raii::string* data = reinterpret_cast<raii::string*>(userdata);
if(size*nmemb > 13 && !strncmp("Content-Type:", ptr, 13)){
(*data) += (ptr + 13);
data->get()[data->length()-1] = 0;
}
return size*nmemb;
}
bool client::create_thumbnail(image_info& info)const{
image_details i;
raii::string reply_header;
if(info.thumb_width > info.width || info.thumb_height > info.height){
info.thumburl = info.fileurl;
info.thumbsize = info.filesize;
info.thumbmime = info.mimetype;
}
m_curl.setopt(CURLOPT_HEADERFUNCTION, _thumbnail_header_callback);
m_curl.setopt(CURLOPT_HEADERDATA, &reply_header);
i.data = _get_curl(m_ses->urls.file_thumbnail(m_ses->homeserver, info.fileurl, info.thumb_width, info.thumb_height, "crop"_ss));
m_curl.setopt(CURLOPT_HEADERFUNCTION, NULL);
m_curl.setopt(CURLOPT_HEADERDATA, NULL);
if(!i.data){
return false;
}
i.width = info.thumb_width;
i.height = info.thumb_height;
i.mimetype = std::move(reply_header);
image_info thumb_data = upload_image(i);
if(!thumb_data.fileurl)
return false;
info.thumburl = std::move(thumb_data.fileurl);
info.thumbsize = thumb_data.filesize;
info.thumbmime = std::move(thumb_data.mimetype);
return true;
}
bool client::create_thumbnail(video_info& info)const{
return create_thumbnail(static_cast<image_info&>(info));
}
/*******************************
Internal functions
********************************/
raii::rjp_string client::_upload_file(raii::filerd& fp, const raii::curl_llist& header)const{
struct internal_upload_data{
const char* data;
size_t len;
};
static size_t _upload_file_read_callback(char* buffer, size_t size, size_t nmemb, void* userdata){
internal_upload_data* src = reinterpret_cast<internal_upload_data*>(userdata);
size_t curl_size = size*nmemb;
size_t to_copy = std::min(curl_size, src->len);
memcpy(buffer, src->data, to_copy);
src->len -= to_copy;
src->data += to_copy;
return to_copy;
}
file_info client::_upload_file(const file_details& file, const raii::curl_llist& header)const{
raii::string fileurl;
file_info retval = {};
internal_upload_data upload_data = {file.data.get(), file.data.length()};
m_curl.postreq();
m_curl.setopt(CURLOPT_POSTFIELDS, NULL);
m_curl.setopt(CURLOPT_READDATA, (void*)fp.get());
m_curl.setopt(CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)fp.length());
m_curl.setopt(CURLOPT_INFILESIZE_LARGE, (curl_off_t)fp.length());
m_curl.setopt(CURLOPT_READFUNCTION, _upload_file_read_callback);
m_curl.setopt(CURLOPT_READDATA, &upload_data);
m_curl.setopt(CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)upload_data.len);
m_curl.setopt(CURLOPT_INFILESIZE_LARGE, (curl_off_t)upload_data.len);
m_curl.seturl(m_ses->urls.file_upload());
m_curl.setheader(header);
m_curl.setopt(CURLOPT_WRITEFUNCTION, _post_reply_curl_callback);
m_curl.setopt(CURLOPT_WRITEDATA, &fileurl);
CURLcode cres = m_curl.perform();
m_curl.setopt(CURLOPT_READDATA, NULL);
if(cres != CURLE_OK)
if(cres != CURLE_OK){
return {};
}
if(!fileurl)
return {};
@ -150,7 +202,11 @@ namespace matrix{
if(!root)
return {};
RJP_search_res res = rjp_search_member(root.get(), "content_uri", 0);
return res.value;
if(!res.value)
return {};
retval.fileurl = res.value;
retval.filename = file.name;
retval.filesize = file.data.length();
return retval;
}
}

View File

@ -18,6 +18,7 @@
#include "matrix/client_url_list.hpp"
#include "raii/string.hpp"
#include "raii/util.hpp"
namespace matrix{
@ -77,6 +78,13 @@ namespace matrix{
const raii::string& client_url_list::profile_picture(void)const{
return m_profile_picture;
}
raii::string client_url_list::file_thumbnail(const raii::string_base& homeserver, const raii::string_base& fileurl, int width, int height, const raii::string_base& method)const{
if(strncmp(fileurl.get(), "mxc://", 6))
return {};
raii::string media(fileurl.get()+6, fileurl.length()-6);
return raii::string(s_proto + homeserver + "/_matrix/media/r0/thumbnail/" + media + "?width=" + raii::itostr(width) + "&height=" + raii::itostr(height) + "&method=" + method);
}
raii::string client_url_list::logout(const raii::string_base& homeserver, const raii::string_base& access_token)const{
return raii::string(s_proto + homeserver + "/_matrix/client/r0/logout?access_token=" + access_token);
}

View File

@ -49,7 +49,9 @@ namespace matrix{
}
size_t connection::_post_reply_curl_callback(char* ptr, size_t size, size_t nmemb, void* userdata){
raii::string* data = reinterpret_cast<raii::string*>(userdata);
(*data) += ptr;
size_t oldlen = data->length();
data->resize(data->length() + (size*nmemb));
memcpy(data->get()+oldlen, ptr, size*nmemb);
return size*nmemb;
}
raii::string connection::_get_curl(const raii::string_base& url)const{

View File

@ -51,6 +51,19 @@ namespace raii{
char* string_base::release(void){
return std::exchange(m_data, nullptr);
}
bool string_base::resize(size_t newsize){
if(newsize < m_length)
return false;
char* newbuf = static_cast<char*>(_allocate(newsize+1));
if(!newbuf) return false;
memset(newbuf, 0, newsize+1);
memcpy(newbuf, m_data, m_length);
newbuf[m_length] = 0;
m_length = newsize;
_free(m_data);
m_data = newbuf;
return true;
}
char& string_base::operator[](size_t i){
return m_data[i];
}