diff --git a/AUTHORS b/AUTHORS index 7a65fd2..66804eb 100644 --- a/AUTHORS +++ b/AUTHORS @@ -4,3 +4,4 @@ Main author: Contributors: Gordon Quad Michael Raitza + nogaems diff --git a/CMakeLists.txt b/CMakeLists.txt index d729a89..2bb4a59 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ add_library(tox MODULE src/twc-message-queue.c src/twc-profile.c src/twc-tox-callbacks.c + src/twc-tfer.c src/twc-utils.c) set_target_properties(tox PROPERTIES diff --git a/README.md b/README.md index e391e34..309413c 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Standard][3]. - Proxy support - Multiple profiles - Encrypted save files + - File transfer ## Installation Tox-WeeChat is tested with [WeeChat][2] 2.1 and [TokTok c-toxcore][5] 0.2.1. diff --git a/src/twc-commands.c b/src/twc-commands.c index d49e023..7ff8700 100644 --- a/src/twc-commands.c +++ b/src/twc-commands.c @@ -19,11 +19,15 @@ #include #include +#include +#include +#include #include #include #include "twc-bootstrap.h" +#include "twc-tfer.h" #include "twc-chat.h" #include "twc-config.h" #include "twc-friend-request.h" @@ -127,6 +131,22 @@ enum TWC_FRIEND_MATCH return WEECHAT_RC_OK; \ } +/** + * Make sure a file exists. + */ +#define TWC_CHECK_FILE_EXISTS(filename) \ + if(access(filename, F_OK) == -1 ) \ + { \ + weechat_printf(NULL, "%sFile \"%s\" does not exist", \ + weechat_prefix("error"), filename); \ + return WEECHAT_RC_ERROR; \ + } + +#define TWC_RETURN_WITH_FILE_ERROR(filename, type) \ + weechat_printf(NULL, "%s\"%s\" must be a regular file or pipe, " \ + "not a %s", weechat_prefix("error"), filename, type); \ + return WEECHAT_RC_ERROR; + /** * Get number of friend matching string. Tries to match number, name and * Tox ID. @@ -1175,6 +1195,125 @@ twc_cmd_tox(const void *pointer, void *data, struct t_gui_buffer *buffer, return WEECHAT_RC_ERROR; } +/** + * Command /send callback. + */ +int +twc_cmd_send(const void *pointer, void *data, struct t_gui_buffer *buffer, + int argc, char **argv, char **argv_eol) +{ + if (argc == 1) + return WEECHAT_RC_ERROR; + struct t_twc_profile *profile = twc_profile_search_buffer(buffer); + TWC_CHECK_PROFILE(profile); + TWC_CHECK_PROFILE_LOADED(profile); + + char recipient[TOX_MAX_NAME_LENGTH + 1] = {0}; + char filename[FILENAME_MAX + 1] = {0}; + size_t filename_arg_num; + + /* /send */ + if (argc == 2) + { + if (profile->buffer == buffer || profile->tfer->buffer == buffer) + { + weechat_printf(profile->buffer, "%s%s", weechat_prefix("error"), "you must specify a friend"); + return WEECHAT_RC_ERROR; + } + snprintf(recipient, TOX_MAX_NAME_LENGTH, "%s", weechat_buffer_get_string(buffer, "name")); + struct t_twc_chat *chat = twc_chat_search_buffer(buffer); + if (chat->group_number != -1) + { + weechat_printf(profile->buffer, "%s%s", weechat_prefix("error"), "the file transmission is " + "allowed only between friends"); + return WEECHAT_RC_ERROR; + } + + char *name = twc_get_name_nt(profile->tox, chat->friend_number); + sprintf(recipient, "%s", name); + filename_arg_num = 1; + free(name); + } + + /* /send || */ + if (argc >= 3) + { + /* do a shell split in case a friend has spaces in his name + * and join the name */ + int shell_argc; + char **shell_argv = weechat_string_split_shell(argv_eol[1], &shell_argc); + for (int i = 0; i < shell_argc -1; i++) + { + strcat(recipient, shell_argv[i]); + if (i < shell_argc - 2) + strcat(recipient, " "); + } + filename_arg_num = shell_argc; + weechat_string_free_split(shell_argv); + } + wordexp_t expanded; + wordexp(argv[filename_arg_num], &expanded, 0); + snprintf(filename, FILENAME_MAX, "%s", expanded.we_wordv[0]); + wordfree(&expanded); + TWC_CHECK_FILE_EXISTS(filename); + + uint32_t friend_number = twc_match_friend(profile, recipient); + TWC_CHECK_FRIEND_NUMBER(profile, (signed) friend_number, recipient); + + struct stat st; + stat(filename, &st); + switch (st.st_mode & S_IFMT) + { + case S_IFBLK: + TWC_RETURN_WITH_FILE_ERROR(filename, "block device"); + case S_IFCHR: + TWC_RETURN_WITH_FILE_ERROR(filename, "character device"); + case S_IFDIR: + TWC_RETURN_WITH_FILE_ERROR(filename, "directory"); + case S_IFSOCK: + TWC_RETURN_WITH_FILE_ERROR(filename, "socket"); + case S_IFREG: + break; + case S_IFLNK: + break; + default: + weechat_printf(NULL, "%sunknown file type", weechat_prefix("error")); + return WEECHAT_RC_ERROR; + } + + char *stripped_name = twc_tfer_file_name_strip(filename, FILENAME_MAX + 1 - strlen(filename)); + + TOX_ERR_FILE_SEND error; + uint32_t file_number = tox_file_send(profile->tox, friend_number, TOX_FILE_KIND_DATA, + S_ISFIFO(st.st_mode) ? UINT64_MAX : (size_t)st.st_size, + NULL, (uint8_t *)stripped_name, strlen(filename), &error); + free(stripped_name); + if (error != TOX_ERR_FILE_SEND_OK) + { + weechat_printf(profile->buffer, "%ssending \"%s\" has been failed: %s", + weechat_prefix("error"), filename, twc_tox_err_file_send(error)); + return WEECHAT_RC_ERROR; + } + if (!(profile->tfer->buffer)) + { + twc_tfer_load(profile); + } + struct t_twc_tfer_file *file = twc_tfer_file_new(profile, recipient, filename, + friend_number, file_number, + st.st_size, TWC_TFER_FILE_TYPE_UPLOADING); + if (!file) + { + weechat_printf(profile->buffer, "%scannot open the file \"%s\"", + weechat_prefix("error"), filename); + return WEECHAT_RC_ERROR; + } + twc_tfer_file_add(profile->tfer, file); + twc_tfer_buffer_update(profile->tfer); + twc_tfer_update_status(profile->tfer, "waiting for action"); + + return WEECHAT_RC_OK; +} + /** * Register Tox-WeeChat commands. */ @@ -1298,4 +1437,12 @@ twc_commands_init() " || unload %(tox_loaded_profiles)|%*" " || reload %(tox_loaded_profiles)|%*", twc_cmd_tox, NULL, NULL); + weechat_hook_command("send", "send a file to a friend", + "" + " || || ", + "file: path to the file\n" + "number, name, Tox ID: the friend you are sending the file to\n", + "%(filename)" + " || %(tox_friend_name)|%(tox_friend_tox_id) %(filename)", + twc_cmd_send, NULL, NULL); } diff --git a/src/twc-config.c b/src/twc-config.c index b072452..54c2101 100644 --- a/src/twc-config.c +++ b/src/twc-config.c @@ -26,6 +26,7 @@ #include "twc-list.h" #include "twc-profile.h" +#include "twc-tfer.h" #include "twc.h" #include "twc-config.h" @@ -41,7 +42,7 @@ struct t_config_option *twc_config_short_id_size; char *twc_profile_option_names[TWC_PROFILE_NUM_OPTIONS] = { "save_file", "autoload", "autojoin", "autojoin_delay", "max_friend_requests", "proxy_address", "proxy_port", "proxy_type", - "udp", "ipv6", "passphrase", "logging", + "udp", "ipv6", "passphrase", "logging", "downloading_path", }; /** @@ -186,7 +187,10 @@ twc_config_profile_change_callback(const void *pointer, void *data, twc_profile_set_logging(item->profile, logging_enabled); } } - + break; + case TWC_PROFILE_OPTION_DOWNLOADING_PATH: + twc_tfer_update_downloading_path(profile); + break; default: break; } @@ -285,6 +289,12 @@ twc_config_init_option(struct t_twc_profile *profile, description = "use UDP when communicating with the Tox network"; default_value = "on"; break; + case TWC_PROFILE_OPTION_DOWNLOADING_PATH: + type = "string"; + description = "path to downloaded files (\"%h\" will be replaced by " + "WeeChat home folder and \"%p\" by profile name"; + default_value = "%h/tfer/%p/"; + break; default: return NULL; } diff --git a/src/twc-list.h b/src/twc-list.h index ac038e7..5a8266f 100644 --- a/src/twc-list.h +++ b/src/twc-list.h @@ -21,6 +21,7 @@ #define TOX_WEECHAT_LIST_H #include +#include "twc-tfer.h" struct t_twc_list { @@ -42,6 +43,7 @@ struct t_twc_list_item struct t_twc_group_chat_invite *group_chat_invite; struct t_twc_chat *chat; struct t_twc_queued_message *queued_message; + struct t_twc_tfer_file *file; }; struct t_twc_list_item *next_item; diff --git a/src/twc-profile.c b/src/twc-profile.c index c65ae6d..d4494d0 100644 --- a/src/twc-profile.c +++ b/src/twc-profile.c @@ -169,9 +169,11 @@ twc_profile_new(const char *name) profile->group_chat_invites = twc_list_new(); profile->message_queues = weechat_hashtable_new( 32, WEECHAT_HASHTABLE_INTEGER, WEECHAT_HASHTABLE_POINTER, NULL, NULL); + profile->tfer = twc_tfer_new(); /* set up config */ twc_config_init_profile(profile); + twc_tfer_update_downloading_path(profile); return profile; } @@ -456,6 +458,10 @@ twc_profile_load(struct t_twc_profile *profile) tox_callback_conference_peer_name(profile->tox, twc_group_peer_name_callback); tox_callback_conference_title(profile->tox, twc_group_title_callback); + tox_callback_file_recv_control(profile->tox, twc_file_recv_control_callback); + tox_callback_file_chunk_request(profile->tox, twc_file_chunk_request_callback); + tox_callback_file_recv(profile->tox, twc_file_recv_callback); + tox_callback_file_recv_chunk(profile->tox, twc_file_recv_chunk_callback); return TWC_RC_OK; } @@ -567,6 +573,8 @@ twc_profile_search_buffer(struct t_gui_buffer *buffer) { if (profile_item->profile->buffer == buffer) return profile_item->profile; + if (profile_item->profile->tfer->buffer == buffer) + return profile_item->profile; size_t chat_index; struct t_twc_list_item *chat_item; @@ -580,6 +588,23 @@ twc_profile_search_buffer(struct t_gui_buffer *buffer) return NULL; } +/** + * Return the profile associated with a tox instance, if any. + */ + +struct t_twc_profile * +twc_profile_search_tox(struct Tox *tox) +{ + size_t profile_index; + struct t_twc_list_item *profile_item; + twc_list_foreach(twc_profiles, profile_index, profile_item) + { + if (profile_item->profile->tox == tox) + return profile_item->profile; + } + return NULL; +} + /** * Enable or disable the WeeChat logger for all buffers for a profile. * @@ -657,11 +682,18 @@ twc_profile_free(struct t_twc_profile *profile) weechat_buffer_set_pointer(profile->buffer, "close_callback", NULL); weechat_buffer_close(profile->buffer); } + /* close tfer's buffer */ + if (profile->tfer->buffer) + { + weechat_buffer_set_pointer(profile->tfer->buffer, "close_callback", NULL); + weechat_buffer_close(profile->tfer->buffer); + } /* free things */ twc_chat_free_list(profile->chats); twc_friend_request_free_list(profile->friend_requests); twc_group_chat_invite_free_list(profile->group_chat_invites); + twc_tfer_free(profile->tfer); twc_message_queue_free_profile(profile); free(profile->name); free(profile); diff --git a/src/twc-profile.h b/src/twc-profile.h index c69f752..eca37f5 100644 --- a/src/twc-profile.h +++ b/src/twc-profile.h @@ -25,6 +25,8 @@ #include #include +#include "twc-tfer.h" + enum t_twc_profile_option { TWC_PROFILE_OPTION_SAVEFILE = 0, @@ -39,6 +41,7 @@ enum t_twc_profile_option TWC_PROFILE_OPTION_IPV6, TWC_PROFILE_OPTION_PASSPHRASE, TWC_PROFILE_OPTION_LOGGING, + TWC_PROFILE_OPTION_DOWNLOADING_PATH, TWC_PROFILE_NUM_OPTIONS, }; @@ -59,6 +62,8 @@ struct t_twc_profile struct t_twc_list *friend_requests; struct t_twc_list *group_chat_invites; struct t_hashtable *message_queues; + + struct t_twc_tfer *tfer; }; extern struct t_twc_list *twc_profiles; @@ -128,6 +133,9 @@ twc_profile_search_name(const char *name); struct t_twc_profile * twc_profile_search_buffer(struct t_gui_buffer *buffer); +struct t_twc_profile * +twc_profile_search_tox(struct Tox *tox); + enum t_twc_rc twc_profile_set_logging(struct t_twc_profile *profile, bool logging); diff --git a/src/twc-tfer.c b/src/twc-tfer.c new file mode 100644 index 0000000..35f43cb --- /dev/null +++ b/src/twc-tfer.c @@ -0,0 +1,843 @@ +/* + * This file is part of Tox-WeeChat. + * + * Tox-WeeChat is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox-WeeChat is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox-WeeChat. If not, see . + */ + +#include +#include +#include +#include + +#include +#include + +#include "twc-tfer.h" +#include "twc-profile.h" +#include "twc-list.h" +#include "twc-utils.h" +#include "twc.h" + +#define PROGRESS_BAR_LEN (50) + +#define TWC_TFER_UPDATE_STATUS_AND_RETURN(fmt, ...) {\ + sprintf(status, fmt, ##__VA_ARGS__); \ + twc_tfer_update_status(profile->tfer, status); \ + weechat_string_free_split(argv); \ + free(status); \ + return WEECHAT_RC_OK; \ + } + +#define TWC_TFER_MESSAGE(present, past) {\ + int result = twc_tfer_file_ ## present(profile, n); \ + switch (result) \ + { \ + case 1: \ + TWC_TFER_UPDATE_STATUS_AND_RETURN("request number %ld has been " #past, n); \ + case 0: \ + TWC_TFER_UPDATE_STATUS_AND_RETURN("request number %ld cannot be " #past " because " \ + "of tox internal issues", n); \ + case -1: \ + TWC_TFER_UPDATE_STATUS_AND_RETURN("request number %ld cannot be " #past, n); \ + } }\ + +/** + * Create a new "tfer" object that handles a list of transmitting files and + * a buffer for managing them. + */ +struct t_twc_tfer * +twc_tfer_new() +{ + struct t_twc_tfer *tfer = malloc(sizeof(struct t_twc_tfer)); + tfer->files = twc_list_new(); + tfer->buffer = NULL; + return tfer; +} + +/** + * Load "tfer" buffer and make it ready for usage. + */ +enum t_twc_rc +twc_tfer_load(struct t_twc_profile *profile) +{ + /* create "tfer" buffer */ + struct t_gui_buffer *buffer; + char *name = malloc(sizeof(profile->name) + 5); + sprintf(name, "tfer/%s", profile->name); + profile->tfer->buffer = buffer = weechat_buffer_new(name, + twc_tfer_buffer_input_callback, + (void *)profile, NULL, + twc_tfer_buffer_close_callback, + (void *)profile, NULL); + free(name); + if (!buffer) + return TWC_RC_ERROR; + /* set all parameters of the buffer*/ + weechat_buffer_set(buffer, "type", "free"); + weechat_buffer_set(buffer, "notify", "1"); + weechat_buffer_set(buffer, "day_change", "0"); + weechat_buffer_set(buffer, "clear", "0"); + weechat_buffer_set(buffer, "nicklist", "0"); + weechat_buffer_set(buffer, "title", "File transmission"); + twc_tfer_print_legend(profile->tfer); + return TWC_RC_OK; +} + +/** + * Display status line and available commands. + */ +int +twc_tfer_print_legend(struct t_twc_tfer *tfer) +{ + char *text[TWC_TFER_LEGEND_LINES] = { + "status: OK", /* This line is reserved for the status */ + "r: refresh | a : accept | d : decline", + "p : pause | c : continue | b : abort", + "files:" + }; + int i; + for (i = 0; i < TWC_TFER_LEGEND_LINES; i++) + { + weechat_printf_y(tfer->buffer, i, "%s", text[i]); + } + return WEECHAT_RC_OK; +} + +/** + * Expand %h and %p in path. + * Returned string must be freed. + */ +char * +twc_tfer_expanded_path(struct t_twc_profile *profile, const char *base_path) +{ + const char *weechat_dir = weechat_info_get("weechat_dir", NULL); + char *home_expanded = weechat_string_replace(base_path, "%h", weechat_dir); + char *full_path = + weechat_string_replace(home_expanded, "%p", profile->name); + free(home_expanded); + return full_path; +} + +/** + * Set profile-associated path for downloads. + * If it is impossible to create a directory with the path that + * has been set in tox.profile..downloading_path then default + * value will be used. + */ +void +twc_tfer_update_downloading_path(struct t_twc_profile *profile) +{ + const char *base_path = TWC_PROFILE_OPTION_STRING(profile, + TWC_PROFILE_OPTION_DOWNLOADING_PATH); + char *full_path = twc_tfer_expanded_path(profile, base_path); + if (!weechat_mkdir_parents(full_path, 0755)) + { + char *bad_path = full_path; + base_path = weechat_config_string(twc_config_profile_default[TWC_PROFILE_OPTION_DOWNLOADING_PATH]); + full_path = twc_tfer_expanded_path(profile, base_path); + weechat_printf(profile->buffer, "cannot create directory \"%s\"," + "using default value: \"%s\"", bad_path, full_path); + free(bad_path); + weechat_mkdir_parents(full_path, 0755); + } + free(profile->tfer->downloading_path); + profile->tfer->downloading_path = full_path; +} + +/** + * Check if there's an access to the file. + */ +bool +twc_tfer_file_check(const char *filename) +{ + return access(filename, F_OK) == -1 ? false : true; +} + +/** + * Add "*().*" to the filename. + * Returns a pointer to allocated string and must be freed after use. + */ +char * +twc_tfer_file_unique_name(const char* original) +{ + char *name = malloc(sizeof(char) * (FILENAME_MAX + 1)); + name[FILENAME_MAX] = '\0'; + /* in case if someone sent way too long filename */ + strncpy(name, original, FILENAME_MAX); + if (!twc_tfer_file_check(name)) + return name; + /* a file with the given name is already exist */ + int i; + char *extension; + char *dot; + if ((dot = strrchr(original, '.'))) + { + name[dot - original] = '\0'; + extension = dot; + } + else + extension = ""; + char body[strlen(name)+1]; + strcpy(body, name); + + /* check if there is already a postfix number in the end of the file + * surrounded by parenthesis */ + char *left, *right; + long number = 1; /* number postfix */ + if ((left = strrchr(body, '(')) && (right = strrchr(body, ')'))) + { + if (!(number = strtol(left + sizeof(char), NULL, 0)) && + *(right - sizeof(char)) != '0') + { + number = 1; + } + else + { + *left = '\0'; + } + } + /* trying names until success */ + i = number; + do + { + snprintf(name, FILENAME_MAX, "%s(%i)%s", body, i, extension); + i++; + } + while (twc_tfer_file_check(name)); + + return name; +} + +/** + * Delete everything before "/" (including "/") from the filename. + * Returns a pointer to allocated string and must be freed after use. + */ +char * +twc_tfer_file_name_strip(const char *original, size_t size) +{ + char *name = malloc(sizeof(char) * size); + char *slash, *offset = (char *)original; + if ((slash = strrchr(original, '/'))) + offset = slash + sizeof(char); + if (strlen(offset)) + { + sprintf(name, "%s", offset); + return name; + } + else + return NULL; +} + +/** + * Create a new file. + */ +struct t_twc_tfer_file * +twc_tfer_file_new(struct t_twc_profile *profile, + const char *nickname, const char *filename, + uint32_t friend_number, uint32_t file_number, + uint64_t size, enum t_twc_tfer_file_type filetype) +{ + struct t_twc_tfer_file *file = malloc(sizeof(struct t_twc_tfer_file)); + file->status = TWC_TFER_FILE_STATUS_REQUEST; + file->type = filetype; + file->position = 0; + file->timestamp = 0; + file->cached_speed = 0; + file->after_last_cache = 0; + file->nickname = strdup(nickname); + file->friend_number = friend_number; + file->file_number = file_number; + file->size = size; + if (filetype == TWC_TFER_FILE_TYPE_DOWNLOADING) + { + char *full_path = malloc(sizeof(char) * (FILENAME_MAX + 1)); + sprintf(full_path, "%s", profile->tfer->downloading_path); + char *final_name = twc_tfer_file_name_strip(filename, + FILENAME_MAX + 1 - strlen(full_path)); + if (!final_name) + return NULL; + + char *slash = strrchr(full_path, '/'); + if (*(slash + sizeof(char)) != '\0') + strcat(full_path, "/"); + + strcat(full_path, final_name); + char *final_path = twc_tfer_file_unique_name(full_path); + + file->filename = strdup(strrchr(final_path, '/') + sizeof(char)); + file->full_path = final_path; + + file->fp = fopen(final_path, "w"); + + free(final_name); + free(full_path); + } + else + { + file->filename = twc_tfer_file_name_strip(filename, + FILENAME_MAX + 1 - strlen(filename)); + file->full_path = NULL; + file->fp = fopen(filename, "r"); + } + + if (!(file->fp)) + return NULL; + return file; + +} + +/** + * Add file to the buffer + */ +void +twc_tfer_file_add(struct t_twc_tfer *tfer, struct t_twc_tfer_file *file) +{ + twc_list_item_new_data_add(tfer->files, file); +} + +/** + * Get file type: "<=" (downloading) or "=>" (uploading). + */ +const char * +twc_tfer_file_get_type_str(struct t_twc_tfer_file *file) +{ + return file->type == TWC_TFER_FILE_TYPE_DOWNLOADING ? "<=" : "=>"; +} + +/** + * Get file status string (excluding TWC_TFER_FILE_STATUS_IN_PROGRESS). + */ +const char * +twc_tfer_file_get_status_str(struct t_twc_tfer_file *file) +{ + char *statuses[] = { + "[request]", + "", + "[paused]", + "[done]", + "[declined]", + "[aborted]" + }; + return statuses[file->status]; +} + +/** + * Get cut file size. + */ +float +twc_tfer_cut_size(size_t size) +{ + float ret = size; + int i = 0; + while((ret>1024) && (i < TWC_MAX_SIZE_SUFFIX)) + { + ret /= 1024.0; + i++; + } + return ret; +} + +/** + * Get file size suffix. + */ +const char * +twc_tfer_size_suffix(uint64_t size) +{ + char *suffixes[] = {"", "K", "M", "G", "T"}; + uint64_t ret = size; + int i = 0; + while((ret > 1024) && (i < TWC_MAX_SIZE_SUFFIX)) + { + ret /= 1024.0; + i++; + } + return suffixes[i]; +} + +/** + * Get cut speed value. + */ +float +twc_tfer_cut_speed(float speed) +{ + float ret = speed; + int i = 0; + while((ret>1024) && (i < TWC_MAX_SPEED_SUFFIX)) + { + ret /= 1024.0; + i++; + } + return ret; +} + +/** + * Get speed suffix. + */ +const char * +twc_tfer_speed_suffix(float speed) +{ + char *suffixes[] = {"bytes/s", "KB/s", "MB/s", "GB/s", "TB/s"}; + uint64_t ret = speed; + int i = 0; + while((ret > 1024) && (i < TWC_MAX_SPEED_SUFFIX)) + { + ret /= 1024.0; + i++; + } + return suffixes[i]; +} + +/** + * Get current time with nanoseconds since the Epoch. + */ +double +twc_tfer_get_time() +{ + struct timespec tp; + clock_gettime(CLOCK_REALTIME, &tp); + return (double)tp.tv_sec + (double)(tp.tv_nsec/1E9); +} + +/** + * Get speed of transmission. + */ +float +twc_tfer_get_speed(struct t_twc_tfer_file *file) +{ + if (file->timestamp == 0) + return 0; + double diff = twc_tfer_get_time() - file->timestamp; + if (diff < 1) + return file->cached_speed; + float result = (float)(file->after_last_cache) / (diff * diff); + file->cached_speed = result; + return result; +} + +/** + * Update buffer strings for a certain file. + */ +void +twc_tfer_file_update(struct t_twc_tfer *tfer, struct t_twc_tfer_file *file) +{ + size_t index = twc_tfer_file_get_index(tfer, file); + size_t line = index * 2 + TWC_TFER_LEGEND_LINES; + const char *type = twc_tfer_file_get_type_str(file); + size_t indent = 0; + size_t remainder = index; + do + { + remainder = remainder / 10; + indent++; + } + while (remainder > 0); + indent += 5; /* length of ") => " */ + char placeholder[indent + 1]; + memset(placeholder, ' ', indent); + placeholder[indent] = '\0'; + const char *status = twc_tfer_file_get_status_str(file); + if (file->size == UINT64_MAX) + { + weechat_printf_y(tfer->buffer, line, "%i) %s %s: %s [STREAM]", + index, type, file->nickname, file->filename); + } + else + { + float display_size = twc_tfer_cut_size(file->size); + const char *size_suffix = twc_tfer_size_suffix(file->size); + weechat_printf_y(tfer->buffer, line, "%i) %s %s: %s %i (%.2f%s)", + index, + type, + file->nickname, + file->filename, + file->size, + display_size, + size_suffix); + } + if (file->status == TWC_TFER_FILE_STATUS_IN_PROGRESS) + { + float speed = twc_tfer_get_speed(file); + float display_speed = twc_tfer_cut_speed(speed); + const char *speed_suffix = twc_tfer_speed_suffix(speed); + if (file->size == UINT64_MAX) + { + weechat_printf_y(tfer->buffer, line + 1, "%s%.2f%s", + placeholder, display_speed, speed_suffix); + return; + } + double ratio = (double)(file->position)/(double)(file->size); + int percents = (int)(ratio * 100); + + char progress_bar[PROGRESS_BAR_LEN+1]; + memset(progress_bar, ' ', PROGRESS_BAR_LEN); + int i; + for (i = 0; i < PROGRESS_BAR_LEN * ratio; i++) + progress_bar[i] = '='; + if (i < PROGRESS_BAR_LEN) + progress_bar[i] = '>'; + progress_bar[PROGRESS_BAR_LEN] = '\0'; + + float display_pos = twc_tfer_cut_size(file->position); + const char *pos_suffix = twc_tfer_size_suffix(file->position); + + weechat_printf_y(tfer->buffer, line + 1, "%s%i%% [%s] %.2f%s %.2f%s", + placeholder, + percents, + progress_bar, + display_pos, + pos_suffix, + display_speed, + speed_suffix); + } + else + weechat_printf_y(tfer->buffer, line + 1, "%s%s", + placeholder,status); +} + +/** + * Allocate and return "uint8_t data[length]" chunk of data starting from "position". + */ +uint8_t * +twc_tfer_file_get_chunk(struct t_twc_tfer_file *file, uint64_t position, size_t length) +{ + fseek(file->fp, position, SEEK_SET); + uint8_t *data = malloc(sizeof(uint8_t) * length); + size_t read = fread(data, sizeof(uint8_t), length, file->fp); + while ((read < length) && !feof(file->fp)) + { + read += fread(data + read * sizeof(uint8_t), sizeof(uint8_t), length - read, file->fp); + } + if (read != length) + return NULL; + return data; +} + +/** + * Write a chunk to the file. + */ +bool +twc_tfer_file_write_chunk(struct t_twc_tfer_file *file, const uint8_t *data, uint64_t position, size_t length) +{ + fseek(file->fp, position, SEEK_SET); + size_t wrote = fwrite(data, sizeof(uint8_t), length, file->fp); + while (wrote < length) + { + wrote += fwrite(data + wrote * sizeof(uint8_t), sizeof(uint8_t), length - wrote, file->fp); + } + + if (wrote != length) + return false; + return true; +} + +/** + * Return file by its "uint32_t file_number". + */ +struct t_twc_tfer_file * +twc_tfer_file_get_by_number(struct t_twc_tfer *tfer, uint32_t file_number) +{ + size_t index; + struct t_twc_list_item *item; + twc_list_foreach(tfer->files, index, item) + { + if (item->file->file_number == file_number) + return item->file; + } + return NULL; +} + +/** + * Return file index. + */ +size_t +twc_tfer_file_get_index(struct t_twc_tfer *tfer, struct t_twc_tfer_file *file) +{ + size_t index; + struct t_twc_list_item *item; + twc_list_foreach(tfer->files, index, item) + { + if (item->file == file) + return index; + } + return SIZE_MAX; +} + +/** + * Update status line of the "tfer" buffer. + */ +int +twc_tfer_update_status(struct t_twc_tfer *tfer, const char *status) +{ + weechat_printf_y(tfer->buffer, 0, "status: %s", status); + weechat_buffer_set(tfer->buffer, "hotlist", WEECHAT_HOTLIST_HIGHLIGHT); + return WEECHAT_RC_OK; +} + +/** + * Update "tfer" buffer + */ +void +twc_tfer_buffer_update(struct t_twc_tfer *tfer) +{ + size_t index; + struct t_twc_list_item *item; + twc_list_foreach(tfer->files, index, item) + { + twc_tfer_file_update(tfer, item->file); + } +} + +/** + * Refresh the entire buffer, i.e. delete files with status of: + * [denied], [aborted], [done]. + */ +void +twc_tfer_buffer_refresh(struct t_twc_tfer *tfer) +{ + size_t index; + struct t_twc_list_item *item; + twc_list_foreach(tfer->files, index, item) + { + enum t_twc_tfer_file_status status = item->file->status; + if (status == TWC_TFER_FILE_STATUS_DECLINED || + status == TWC_TFER_FILE_STATUS_ABORTED || + status == TWC_TFER_FILE_STATUS_DONE) + { + struct t_twc_tfer_file *file = twc_list_remove(item); + twc_tfer_file_free(file); + } + } + weechat_buffer_clear(tfer->buffer); + twc_tfer_print_legend(tfer); + twc_tfer_buffer_update(tfer); +} + +/** + * Send TOX_FILE_CONTROL command to a client. + * "сheck" is a file status that a file should be in before sending a control command. + * "send" is a control comand you are going to send. + * "set" is a file status that will be set after successful sending a control command. + */ +int +twc_tfer_file_send_control(struct t_twc_profile *profile, size_t index, + enum t_twc_tfer_file_status check, + enum TOX_FILE_CONTROL send, + enum t_twc_tfer_file_status set) +{ + struct t_twc_tfer_file *file; + struct t_twc_list_item *item = twc_list_get(profile->tfer->files, index); + file = item->file; + if (file->status != check) + return -1; + if (file->type == TWC_TFER_FILE_TYPE_UPLOADING && + send == TOX_FILE_CONTROL_RESUME) + return -1; + enum TOX_ERR_FILE_CONTROL control_error; + tox_file_control(profile->tox, file->friend_number, file->file_number, send, + &control_error); + if (control_error) + { + weechat_printf(profile->buffer, "%scannot send control command for \"%s\" file: %s", + weechat_prefix("error"), file->filename, twc_tox_err_file_control(control_error)); + return 0; + } + else + { + if (send == TOX_FILE_CONTROL_CANCEL) + { + fclose(file->fp); + if (file->type == TWC_TFER_FILE_TYPE_DOWNLOADING && file->size != UINT64_MAX) + remove(file->full_path); + } + file->status = set; + twc_tfer_file_update(profile->tfer, file); + return 1; + } +} + + + +/** + * Accept a file with number in the list. + * Returns 1 if successful, 0 when there's an issue with tox calls + * and -1 if the request is already accepted or declined. + */ +int +twc_tfer_file_accept(struct t_twc_profile *profile, size_t index) +{ + return twc_tfer_file_send_control(profile, index, + TWC_TFER_FILE_STATUS_REQUEST, + TOX_FILE_CONTROL_RESUME, + TWC_TFER_FILE_STATUS_IN_PROGRESS); +} + +/** + * Decline a file with number in the list. + * Returns 1 if successful, 0 when there's an issue with tox calls + * and -1 if the request is already accepted or declined. + */ +int +twc_tfer_file_decline(struct t_twc_profile *profile, size_t index) +{ + return twc_tfer_file_send_control(profile, index, + TWC_TFER_FILE_STATUS_REQUEST, + TOX_FILE_CONTROL_CANCEL, + TWC_TFER_FILE_STATUS_DECLINED); +} + +/** + * Pause transmission of the file with number in the list. + * Returns 1 if successful, 0 when there's an issue with tox calls + * and -1 if transmission is already paused. + */ +int +twc_tfer_file_pause(struct t_twc_profile *profile, size_t index) +{ + return twc_tfer_file_send_control(profile, index, + TWC_TFER_FILE_STATUS_IN_PROGRESS, + TOX_FILE_CONTROL_PAUSE, + TWC_TFER_FILE_STATUS_PAUSED); +} + +/** + * Continue transmission of the file with number in the list. + * Returns 1 if successful, 0 when there's an issue with tox calls + * and -1 if transmission is already going on. + */ +int +twc_tfer_file_continue(struct t_twc_profile *profile, size_t index) +{ + return twc_tfer_file_send_control(profile, index, + TWC_TFER_FILE_STATUS_PAUSED, + TOX_FILE_CONTROL_RESUME, + TWC_TFER_FILE_STATUS_IN_PROGRESS); +} + +/** + * Abort transmission of the file with number in the list. + * Returns 1 if successful, 0 when there's an issue with tox calls + * and -1 if transmission is already aborted. + */ +int +twc_tfer_file_abort(struct t_twc_profile *profile, size_t index) +{ + return twc_tfer_file_send_control(profile, index, + TWC_TFER_FILE_STATUS_IN_PROGRESS, + TOX_FILE_CONTROL_CANCEL, + TWC_TFER_FILE_STATUS_ABORTED); +} + +/** + * Called when input text is entered on buffer. + */ +int +twc_tfer_buffer_input_callback(const void *pointer, void *data, + struct t_gui_buffer *buffer, + const char *input_data) +{ + struct t_twc_profile *profile; + profile = (struct t_twc_profile *)pointer; + int argc; + char **argv = weechat_string_split_shell(input_data, &argc); + char *status = malloc(sizeof(char) * weechat_window_get_integer(weechat_current_window(), "win_width") + 1); + + /* refresh file list, i.e delete files that have been marked as "denied", "aborted" and "done" */ + if (weechat_strcasecmp(argv[0], "r") == 0) + { + if (argc == 1) + { + twc_tfer_buffer_refresh(profile->tfer); + TWC_TFER_UPDATE_STATUS_AND_RETURN("refreshed"); + } + else + { + TWC_TFER_UPDATE_STATUS_AND_RETURN("this command doesn't accept any arguments"); + } + } + if (strstr("adpcbADPCB", argv[0]) && argc < 2) + TWC_TFER_UPDATE_STATUS_AND_RETURN("too few arguments"); + if (argc == 2) + { + size_t n = (size_t)strtol(argv[1], NULL, 0); + if ((n == 0 && strcmp(argv[1], "0") != 0) || n > (profile->tfer->files->count - 1)) + { + TWC_TFER_UPDATE_STATUS_AND_RETURN(" must be existing number of file"); + } + /* accept */ + if (weechat_strcasecmp(argv[0], "a") == 0) + { + TWC_TFER_MESSAGE(accept, accepted); + } + /* decline */ + if (weechat_strcasecmp(argv[0], "d") == 0) + { + TWC_TFER_MESSAGE(decline, declined); + } + /* pause */ + if (weechat_strcasecmp(argv[0], "p") == 0) + { + TWC_TFER_MESSAGE(pause, paused); + } + /* continue */ + if (weechat_strcasecmp(argv[0], "c") == 0) + { + TWC_TFER_MESSAGE(continue, continued); + } + /* abort */ + if (weechat_strcasecmp(argv[0], "b") == 0) + { + TWC_TFER_MESSAGE(abort, aborted); + } + } + if (argc > 2) + { + TWC_TFER_UPDATE_STATUS_AND_RETURN("too many arguments"); + } + TWC_TFER_UPDATE_STATUS_AND_RETURN("unknown command: %s", argv[0]); +} + +/** + * Called when buffer is closed. + */ +int +twc_tfer_buffer_close_callback(const void *pointer, void *data, + struct t_gui_buffer *buffer) +{ + struct t_twc_profile *profile = (struct t_twc_profile *)pointer; + profile->tfer->buffer = NULL; + return WEECHAT_RC_OK; +} + +void +twc_tfer_file_free(struct t_twc_tfer_file *file) +{ + free(file->filename); + free(file->nickname); + if (file->full_path) + free(file->full_path); + free(file); +} + +void +twc_tfer_free(struct t_twc_tfer *tfer) +{ + struct t_twc_tfer_file *file; + while ((file = twc_list_pop(tfer->files))) + { + twc_tfer_file_free(file); + } + free(tfer->files); + free(tfer->downloading_path); + free(tfer); +} diff --git a/src/twc-tfer.h b/src/twc-tfer.h new file mode 100644 index 0000000..0c67c49 --- /dev/null +++ b/src/twc-tfer.h @@ -0,0 +1,159 @@ +/* + * This file is part of Tox-WeeChat. + * + * Tox-WeeChat is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox-WeeChat is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox-WeeChat. If not, see . + */ + +#ifndef TOX_WEECHAT_TFER_H +#define TOX_WEECHAT_TFER_H + +#include +#include +#include + +#include +#include + +#include "twc-list.h" +#include "twc-profile.h" + +#define TWC_TFER_LEGEND_LINES (4) +#define TWC_TFER_FILE_STATUS_MAX_LENGTH (256) +#define TWC_MAX_CHUNK_LENGTH (1371) +#define TWC_MAX_SIZE_SUFFIX (5) +#define TWC_MAX_SPEED_SUFFIX (5) + +enum t_twc_tfer_file_status +{ + TWC_TFER_FILE_STATUS_REQUEST, + TWC_TFER_FILE_STATUS_IN_PROGRESS, + TWC_TFER_FILE_STATUS_PAUSED, + TWC_TFER_FILE_STATUS_DONE, + TWC_TFER_FILE_STATUS_DECLINED, + TWC_TFER_FILE_STATUS_ABORTED, +}; + +enum t_twc_tfer_file_type +{ + TWC_TFER_FILE_TYPE_DOWNLOADING, + TWC_TFER_FILE_TYPE_UPLOADING, +}; + +struct t_twc_tfer_file +{ + enum t_twc_tfer_file_status status; + enum t_twc_tfer_file_type type; + uint64_t position; /* already transmitted (in bytes) */ + uint64_t size; + uint32_t friend_number; + uint32_t file_number; + FILE *fp; + char *filename; + char *full_path; + char *nickname; + double timestamp; + float cached_speed; + size_t after_last_cache; +}; + +struct t_twc_tfer +{ + struct t_twc_list *files; + struct t_gui_buffer *buffer; + char *downloading_path; +}; + +int +twc_tfer_buffer_input_callback(const void *pointer, void *data, + struct t_gui_buffer *weechat_buffer, + const char *input_data); +int +twc_tfer_buffer_close_callback(const void *pointer, void *data, + struct t_gui_buffer *weechat_buffer); +struct t_twc_tfer * +twc_tfer_new(); + +enum t_twc_rc +twc_tfer_load(struct t_twc_profile *profile); + +bool +twc_tfer_has_buffer(struct t_twc_profile *profile); + +int +twc_tfer_print_legend(struct t_twc_tfer *tfer); + +double +twc_tfer_get_time(); + +void +twc_tfer_update_downloading_path(struct t_twc_profile *profile); + +char * +twc_tfer_file_name_strip(const char *original, size_t size); + +struct t_twc_tfer_file * +twc_tfer_file_new(struct t_twc_profile *profile, + const char *nickname, const char *filename, + uint32_t friend_number, uint32_t file_number, + uint64_t size, enum t_twc_tfer_file_type filetype); + +void +twc_tfer_file_add(struct t_twc_tfer *tfer, struct t_twc_tfer_file *file); + +uint8_t * +twc_tfer_file_get_chunk(struct t_twc_tfer_file *file, uint64_t position, size_t length); + +bool +twc_tfer_file_write_chunk(struct t_twc_tfer_file *file, const uint8_t *data, uint64_t position, size_t length); + +struct t_twc_tfer_file * +twc_tfer_file_get_by_number(struct t_twc_tfer *tfer, uint32_t file_number); + +size_t +twc_tfer_file_get_index(struct t_twc_tfer *tfer, struct t_twc_tfer_file *file); + +void +twc_tfer_file_update(struct t_twc_tfer *tfer, struct t_twc_tfer_file *file); + +int +twc_tfer_file_accept(struct t_twc_profile *profile, size_t index); + +int +twc_tfer_file_decline(struct t_twc_profile *profile, size_t index); + +int +twc_tfer_file_pause(struct t_twc_profile *profile, size_t index); + +int +twc_tfer_file_continue(struct t_twc_profile *profile, size_t index); + +int +twc_tfer_file_abort(struct t_twc_profile *profile, size_t index); + +int +twc_tfer_update_status(struct t_twc_tfer *tfer, const char *status); + +void +twc_tfer_buffer_update(struct t_twc_tfer *tfer); + +void +twc_tfer_file_err_send_message(char *message, enum TOX_ERR_FILE_SEND error); + +void +twc_tfer_file_free(struct t_twc_tfer_file *file); + +void +twc_tfer_free(struct t_twc_tfer *tfer); + +#endif /* TOX_WEECHAT_TFER_H */ diff --git a/src/twc-tox-callbacks.c b/src/twc-tox-callbacks.c index 23afb6a..2af2385 100644 --- a/src/twc-tox-callbacks.c +++ b/src/twc-tox-callbacks.c @@ -33,10 +33,15 @@ #include "twc-message-queue.h" #include "twc-profile.h" #include "twc-utils.h" +#include "twc-tfer.h" #include "twc.h" #include "twc-tox-callbacks.h" +#define TWC_TFER_FILE_UPDATE_STATUS(st) \ + file->status = st; \ + twc_tfer_file_update(profile->tfer, file); + int twc_do_timer_cb(const void *pointer, void *data, int remaining_calls) { @@ -228,6 +233,22 @@ twc_name_change_callback(Tox *tox, uint32_t friend_number, const uint8_t *name, weechat_printf(profile->buffer, "%s%s is now known as %s", weechat_prefix("network"), old_name, new_name); + if (profile->tfer->buffer) + { + size_t index; + struct t_twc_list_item *item; + struct t_twc_tfer_file *file; + twc_list_foreach(profile->tfer->files, index, item) + { + file = item->file; + if (file->friend_number == friend_number) + { + free(file->nickname); + file->nickname = strdup(new_name); + twc_tfer_file_update(profile->tfer, item->file); + } + } + } } free(old_name); @@ -564,6 +585,180 @@ twc_group_title_callback(Tox *tox, uint32_t group_number, uint32_t peer_number, free(topic); } +void +twc_file_recv_control_callback(Tox *tox, uint32_t friend_number, uint32_t file_number, + TOX_FILE_CONTROL control, void *user_data) +{ + struct t_twc_profile *profile = twc_profile_search_tox(tox); + struct t_twc_tfer_file *file = twc_tfer_file_get_by_number(profile->tfer, file_number); + if (!file) + { + weechat_printf(profile->tfer->buffer, "%sthere is no file with number %i in queue", + weechat_prefix("error"), file_number); + return; + } + switch (control) + { + case TOX_FILE_CONTROL_RESUME: + TWC_TFER_FILE_UPDATE_STATUS(TWC_TFER_FILE_STATUS_IN_PROGRESS); + break; + case TOX_FILE_CONTROL_PAUSE: + if (file->position !=0) + TWC_TFER_FILE_UPDATE_STATUS(TWC_TFER_FILE_STATUS_PAUSED); + break; + case TOX_FILE_CONTROL_CANCEL: + fclose(file->fp); + if (file->type == TWC_TFER_FILE_TYPE_DOWNLOADING && file->size != UINT64_MAX) + remove(file->full_path); + if (file->position != 0) + { + TWC_TFER_FILE_UPDATE_STATUS(TWC_TFER_FILE_STATUS_ABORTED); + } + else + { + TWC_TFER_FILE_UPDATE_STATUS(TWC_TFER_FILE_STATUS_DECLINED); + } + break; + } +} + +void +twc_file_chunk_request_callback(Tox *tox, uint32_t friend_number, uint32_t file_number, + uint64_t position, size_t length, void *user_data) +{ + struct t_twc_profile *profile = twc_profile_search_tox(tox); + struct t_twc_tfer_file *file = twc_tfer_file_get_by_number(profile->tfer, file_number); + /* the file is missing */ + if (!file) + { + weechat_printf(profile->tfer->buffer, "%sthere is no file with number %i in queue", + weechat_prefix("error"), file_number); + return; + } + /* 0-length chunk requested that means the file transmission is completed */ + if (length == 0) + { + TWC_TFER_FILE_UPDATE_STATUS(TWC_TFER_FILE_STATUS_DONE); + + /* This friend_number will be re-used and re-assigned for another file, + * to prevent collisions in twc_tfer_file_get_by_number calls let's + * set it to a value that won't be used by toxcore. + */ + file->file_number = UINT32_MAX; + fclose(file->fp); + return; + } + uint8_t *data = twc_tfer_file_get_chunk(file, position, length); + if (!data) + { + weechat_printf(profile->buffer, "%serror while reading the file %s", + weechat_prefix("error"), file->filename); + return; + } + enum TOX_ERR_FILE_SEND_CHUNK error; + tox_file_send_chunk(profile->tox, friend_number, file_number, position, data, length, &error); + if (error) + weechat_printf(profile->buffer, "%s%s: chunk sending error: %s", + weechat_prefix("error"), file->filename, twc_tox_err_file_send_chunk(error)); + else + { + file->position += length; + file->after_last_cache += length; + TWC_TFER_FILE_UPDATE_STATUS(TWC_TFER_FILE_STATUS_IN_PROGRESS); + if ((twc_tfer_get_time() - file->timestamp) > 1) + { + file->timestamp = twc_tfer_get_time(); + file->after_last_cache = 0; + } + } + free(data); +} + +void +twc_file_recv_callback(Tox *tox, uint32_t friend_number, uint32_t file_number, + uint32_t kind, uint64_t file_size, const uint8_t *filename, + size_t filename_length, void *user_data) +{ + struct t_twc_profile *profile = twc_profile_search_tox(tox); + if (kind == TOX_FILE_KIND_AVATAR) + { + TOX_ERR_FILE_CONTROL error; + tox_file_control(tox, friend_number, file_number, TOX_FILE_CONTROL_CANCEL, &error); + if (error) + { + weechat_printf(profile->buffer, "%scannot cancel avatar receiving", + weechat_prefix("error")); + } + return; + } + char *name = twc_get_name_nt(tox, friend_number); + char *fname = twc_null_terminate(filename, filename_length); + struct t_twc_tfer_file *file = twc_tfer_file_new(profile, name, fname, + friend_number, file_number, + file_size, TWC_TFER_FILE_TYPE_DOWNLOADING); + free(name); + free(fname); + if (!file) + { + weechat_printf(profile->buffer, "%scannot open the file \"%s\" with write permissions", + weechat_prefix("error"), filename); + return; + } + if (!(profile->tfer->buffer)) + { + twc_tfer_load(profile); + } + twc_tfer_file_add(profile->tfer, file); + twc_tfer_file_update(profile->tfer, file); + twc_tfer_update_status(profile->tfer, "waiting for action"); +} + +void +twc_file_recv_chunk_callback(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, + const uint8_t *data, size_t length, void *user_data) +{ + struct t_twc_profile *profile = twc_profile_search_tox(tox); + struct t_twc_tfer_file *file = twc_tfer_file_get_by_number(profile->tfer, file_number); + /* the file is missing */ + if (!file) + { + weechat_printf(profile->tfer->buffer, "%sthere is no file with number %i in queue", + weechat_prefix("error"), file_number); + return; + } + /* 0-length chunk transmitted that means the file transmission is completed */ + if (length == 0) + { + TWC_TFER_FILE_UPDATE_STATUS(TWC_TFER_FILE_STATUS_DONE); + + /* This friend_number will be re-used and re-assigned for another file, + * to prevent collisions in twc_tfer_file_get_by_number calls let's + * set it to a value that won't be used by toxcore. + */ + file->file_number = UINT32_MAX; + fclose(file->fp); + return; + } + bool result = twc_tfer_file_write_chunk(file, data, position, length); + if (!result) + { + weechat_printf(profile->buffer, "%serror while writing the file %s", + weechat_prefix("error"), file->filename); + return; + } + else + { + file->position += length; + file->after_last_cache += length; + twc_tfer_file_update(profile->tfer, file); + if ((twc_tfer_get_time() - file->timestamp) > 1) + { + file->timestamp = twc_tfer_get_time(); + file->after_last_cache = 0; + } + } +} + #ifndef NDEBUG void twc_tox_log_callback(Tox *tox, TOX_LOG_LEVEL level, const char *file, diff --git a/src/twc-tox-callbacks.h b/src/twc-tox-callbacks.h index 86cd237..f42d8af 100644 --- a/src/twc-tox-callbacks.h +++ b/src/twc-tox-callbacks.h @@ -71,11 +71,26 @@ twc_group_peer_name_callback(Tox *tox, uint32_t group_number, size_t nick_len, void *data); - void twc_group_title_callback(Tox *tox, uint32_t group_number, uint32_t peer_number, const uint8_t *title, size_t length, void *data); +void +twc_file_recv_control_callback(Tox *tox, uint32_t friend_number, uint32_t file_number, TOX_FILE_CONTROL control, + void *user_data); + +void +twc_file_chunk_request_callback(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, + size_t length, void *user_data); + +void +twc_file_recv_callback(Tox *tox, uint32_t friend_number, uint32_t file_number, uint32_t kind, uint64_t file_size, + const uint8_t *filename, size_t filename_length, void *user_data); + +void +twc_file_recv_chunk_callback(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, + const uint8_t *data, size_t length, void *user_data); + #ifndef NDEBUG void twc_tox_log_callback(Tox *tox, TOX_LOG_LEVEL level, const char *file, diff --git a/src/twc-utils.c b/src/twc-utils.c index e16d0e4..e3da381 100644 --- a/src/twc-utils.c +++ b/src/twc-utils.c @@ -232,3 +232,82 @@ twc_set_buffer_logging(struct t_gui_buffer *buffer, bool logging) return weechat_hook_signal_send(signal, WEECHAT_HOOK_SIGNAL_POINTER, buffer); } + +/** + * These following twc_tox_err_file_* functions convert enum TOX_ERR_FILE_* + * error codes to meaningful messages of type char *. + */ + +char * +twc_tox_err_file_control(enum TOX_ERR_FILE_CONTROL error) +{ + char *messages[] = { + "success", + "the friend number passed did not designate a valid friend", + "this client is currently not connected to the friend", + "no file transfer with the given file number was found for the given friend", + "a RESUME control was sent, but the file transfer is running normally", + "A RESUME control was sent, but the file transfer was paused by the other party", + "a PAUSE control was sent, but the file transfer was already paused", + "packet queue is full" + }; + return messages[error]; +} + +char * +twc_tox_err_file_get(enum TOX_ERR_FILE_GET error) +{ + char *messages[] = { + "success", + "one of the arguments to the function was NULL when it was not expected", + "the friend number passed did not designate a valid friend", + "no file transfer with the given number was found for the given friend" + }; + return messages[error]; +} + +char * +twc_tox_err_file_seek(enum TOX_ERR_FILE_SEEK error) +{ + char *messages[] = { + "success", + "the friend number passed did not designate a valid friend", + "the client is currently not connected to the friend", + "no file transfer with the given file number was found for the given friend", + "file was not in a state where it could be seeked", + "seek position was invalid", + "packet queue is full" + }; + return messages[error]; +} + +char * +twc_tox_err_file_send(enum TOX_ERR_FILE_SEND error) +{ + char *messages[] = { + "success", + "one of the arguments of the function was NULL when it was not expected", + "the friend number passed did not designate a valid friend", + "this client is currently not connected to the friend", + "filename lenth exceeded TOX_MAX_FILENAME_LENGTH bytes", + "too many ongoing transfers" + }; + return messages[error]; +} + +char * +twc_tox_err_file_send_chunk(enum TOX_ERR_FILE_SEND_CHUNK error) +{ + char *messages[] = { + "success", + "the length parameter was non-zero, but data was NULL", + "the friend number passed did not designate a valid friend", + "this client is currently not connected to the friend", + "no file transfer with the given file number was found for the given friend", + "not called from the request chunk callback", + "attempted to send more or less data than requested", + "packet queue is full", + "position parameter was wrong" + }; + return messages[error]; +} diff --git a/src/twc-utils.h b/src/twc-utils.h index f405f97..b1d2f3d 100644 --- a/src/twc-utils.h +++ b/src/twc-utils.h @@ -58,4 +58,19 @@ twc_fit_utf8(const char *str, int max); int twc_set_buffer_logging(struct t_gui_buffer *buffer, bool logging); +char * +twc_tox_err_file_control(enum TOX_ERR_FILE_CONTROL error); + +char * +twc_tox_err_file_get(enum TOX_ERR_FILE_GET error); + +char * +twc_tox_err_file_seek(enum TOX_ERR_FILE_SEEK error); + +char * +twc_tox_err_file_send(enum TOX_ERR_FILE_SEND error); + +char * +twc_tox_err_file_send_chunk(enum TOX_ERR_FILE_SEND_CHUNK error); + #endif /* TOX_WEECHAT_UTILS_H */