diff --git a/include/matrix/client.hpp b/include/matrix/client.hpp index bded2a6..feed25e 100644 --- a/include/matrix/client.hpp +++ b/include/matrix/client.hpp @@ -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; }; } diff --git a/include/matrix/client_url_list.hpp b/include/matrix/client_url_list.hpp index 505875c..88584cb 100644 --- a/include/matrix/client_url_list.hpp +++ b/include/matrix/client_url_list.hpp @@ -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; diff --git a/include/matrix/upload_info.hpp b/include/matrix/upload_info.hpp index d930852..70db1cc 100644 --- a/include/matrix/upload_info.hpp +++ b/include/matrix/upload_info.hpp @@ -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; diff --git a/include/raii/string_base.hpp b/include/raii/string_base.hpp index a129d4a..598d776 100644 --- a/include/raii/string_base.hpp +++ b/include/raii/string_base.hpp @@ -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(Allocator::copy(data, len+1)), len){} string_intermediary(const char* data): string_base(strlen(data)) { diff --git a/src/matrix/client.cpp b/src/matrix/client.cpp index d30a903..1cb4d95 100644 --- a/src/matrix/client.cpp +++ b/src/matrix/client.cpp @@ -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(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(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(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; } } diff --git a/src/matrix/client_url_list.cpp b/src/matrix/client_url_list.cpp index 4315740..04bd1e1 100644 --- a/src/matrix/client_url_list.cpp +++ b/src/matrix/client_url_list.cpp @@ -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); } diff --git a/src/matrix/connection.cpp b/src/matrix/connection.cpp index 8dccf10..91fa649 100644 --- a/src/matrix/connection.cpp +++ b/src/matrix/connection.cpp @@ -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(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{ diff --git a/src/raii/string_base.cpp b/src/raii/string_base.cpp index eabd5a5..0679051 100644 --- a/src/raii/string_base.cpp +++ b/src/raii/string_base.cpp @@ -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(_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]; }