Implement file transfer feature
This will add separate "tfer" buffer with all necessary controls for managing files and /send command to start transfer, which you can use either from profile buffer or from private messages buffer; in case of incoming files the buffer will be created as well if doesn't exist yet. Every callbacks were defined according to the specification and this implementation is fully corresponding to the documented behavior including streams.
This commit is contained in:
parent
b1765af433
commit
ecf3467e76
14 changed files with 1511 additions and 3 deletions
1
AUTHORS
1
AUTHORS
|
@ -4,3 +4,4 @@ Main author:
|
|||
Contributors:
|
||||
Gordon Quad <gordon@nowhere>
|
||||
Michael Raitza <spacefrogg-devel@meterriblecrew.net>
|
||||
nogaems <nomad@ag.ru>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -19,11 +19,15 @@
|
|||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <wordexp.h>
|
||||
|
||||
#include <tox/tox.h>
|
||||
#include <weechat/weechat-plugin.h>
|
||||
|
||||
#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 <file> */
|
||||
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 <number>|<name>|<Tox ID> <file> */
|
||||
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>"
|
||||
" || <number>|<name>|<Tox ID> <file>",
|
||||
"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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#define TOX_WEECHAT_LIST_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
#include <tox/tox.h>
|
||||
#include <weechat/weechat-plugin.h>
|
||||
|
||||
#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);
|
||||
|
||||
|
|
843
src/twc-tfer.c
Normal file
843
src/twc-tfer.c
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <tox/tox.h>
|
||||
#include <weechat/weechat-plugin.h>
|
||||
|
||||
#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 <n>: accept | d <n>: decline",
|
||||
"p <n>: pause | c <n>: continue | b <n>: 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.<name>.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 "*(<number>).*" 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 <index> 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 <index> 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 <index> 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 <index> 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 <index> 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("<n> 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);
|
||||
}
|
159
src/twc-tfer.h
Normal file
159
src/twc-tfer.h
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TOX_WEECHAT_TFER_H
|
||||
#define TOX_WEECHAT_TFER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <tox/tox.h>
|
||||
#include <weechat/weechat-plugin.h>
|
||||
|
||||
#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 */
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
Loading…
Reference in a new issue