From 6f8f6f0ac5310aa0311b788d96bb5a5eef028f9f Mon Sep 17 00:00:00 2001 From: jfreegman Date: Wed, 22 Dec 2021 14:26:07 -0500 Subject: [PATCH] Implement file transfer queue for offline friends File transfers initiated for offline friends are now added to a queue and initiated all at once when the friend appears online. --- src/chat.c | 1 + src/chat_commands.c | 63 +++++++++++++++++++----- src/file_transfers.c | 114 +++++++++++++++++++++++++++++++++++-------- src/file_transfers.h | 43 ++++++++++++++-- src/friendlist.c | 8 +-- src/friendlist.h | 5 +- src/windows.c | 6 +-- 7 files changed, 193 insertions(+), 47 deletions(-) diff --git a/src/chat.c b/src/chat.c index ad383eb..5766859 100644 --- a/src/chat.c +++ b/src/chat.c @@ -248,6 +248,7 @@ static void chat_onConnectionChange(ToxWindow *self, Tox *m, uint32_t num, Tox_C if (prev_status == TOX_CONNECTION_NONE) { chat_resume_file_senders(self, m, num); + file_send_queue_check(self, m, self->num); msg = "has come online"; line_info_add(self, true, nick, NULL, CONNECTION, 0, GREEN, msg); diff --git a/src/chat_commands.c b/src/chat_commands.c index 7c13bcf..8b29afd 100644 --- a/src/chat_commands.c +++ b/src/chat_commands.c @@ -54,7 +54,13 @@ void cmd_cancelfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*ar return; } - struct FileTransfer *ft = NULL; + // first check transfer queue + if (file_send_queue_remove(self->num, idx) == 0) { + line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "Pending file transfer removed from queue"); + return; + } + + FileTransfer *ft = NULL; /* cancel an incoming file transfer */ if (strcasecmp(inoutstr, "in") == 0) { @@ -239,7 +245,7 @@ void cmd_savefile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv return; } - struct FileTransfer *ft = get_file_transfer_struct_index(self->num, idx, FILE_TRANSFER_RECV); + FileTransfer *ft = get_file_transfer_struct_index(self->num, idx, FILE_TRANSFER_RECV); if (!ft) { line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "No pending file transfers with that ID."); @@ -324,7 +330,7 @@ void cmd_sendfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv FILE *file_to_send = fopen(path, "r"); if (file_to_send == NULL) { - line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "File not found."); + line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "File `%s` not found.", path); return; } @@ -347,7 +353,7 @@ void cmd_sendfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv goto on_send_error; } - struct FileTransfer *ft = new_file_transfer(self, self->num, filenum, FILE_TRANSFER_SEND, TOX_FILE_KIND_DATA); + FileTransfer *ft = new_file_transfer(self, self->num, filenum, FILE_TRANSFER_SEND, TOX_FILE_KIND_DATA); if (!ft) { err = TOX_ERR_FILE_SEND_TOO_MANY; @@ -367,29 +373,62 @@ void cmd_sendfile(WINDOW *window, ToxWindow *self, Tox *m, int argc, char (*argv on_send_error: + fclose(file_to_send); + switch (err) { - case TOX_ERR_FILE_SEND_FRIEND_NOT_FOUND: + case TOX_ERR_FILE_SEND_FRIEND_NOT_FOUND: { errmsg = "File transfer failed: Invalid friend."; break; + } - case TOX_ERR_FILE_SEND_FRIEND_NOT_CONNECTED: - errmsg = "File transfer failed: Friend is offline."; - break; + case TOX_ERR_FILE_SEND_FRIEND_NOT_CONNECTED: { + int queue_idx = file_send_queue_add(self->num, path, path_len); - case TOX_ERR_FILE_SEND_NAME_TOO_LONG: + char msg[MAX_STR_SIZE]; + + switch (queue_idx) { + case -1: { + snprintf(msg, sizeof(msg), "Invalid file name: path is null or length is zero."); + break; + } + + case -2: { + snprintf(msg, sizeof(msg), "File name is too long."); + break; + } + + case -3: { + snprintf(msg, sizeof(msg), "File send queue is full."); + break; + } + + default: { + snprintf(msg, sizeof(msg), "File transfer queued. Type \"/cancel out %d\" to cancel.", queue_idx); + break; + } + } + + line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s", msg); + + return; + } + + case TOX_ERR_FILE_SEND_NAME_TOO_LONG: { errmsg = "File transfer failed: Filename is too long."; break; + } - case TOX_ERR_FILE_SEND_TOO_MANY: + case TOX_ERR_FILE_SEND_TOO_MANY: { errmsg = "File transfer failed: Too many concurrent file transfers."; break; + } - default: + default: { errmsg = "File transfer failed."; break; + } } line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%s", errmsg); tox_file_control(m, self->num, filenum, TOX_FILE_CONTROL_CANCEL, NULL); - fclose(file_to_send); } diff --git a/src/file_transfers.c b/src/file_transfers.c index d2acf35..1e67ec2 100644 --- a/src/file_transfers.c +++ b/src/file_transfers.c @@ -25,6 +25,7 @@ #include #include +#include "execute.h" #include "file_transfers.h" #include "friendlist.h" #include "line_info.h" @@ -98,7 +99,7 @@ void print_progress_bar(ToxWindow *self, double bps, double pct_done, uint32_t l free(full_line); } -static void refresh_progress_helper(ToxWindow *self, struct FileTransfer *ft) +static void refresh_progress_helper(ToxWindow *self, FileTransfer *ft) { if (ft->state == FILE_TRANSFER_INACTIVE) { return; @@ -126,8 +127,8 @@ bool refresh_file_transfer_progress(ToxWindow *self, uint32_t friendnumber) bool active = false; for (size_t i = 0; i < MAX_FILES; ++i) { - struct FileTransfer *ft_r = &Friends.list[friendnumber].file_receiver[i]; - struct FileTransfer *ft_s = &Friends.list[friendnumber].file_sender[i]; + FileTransfer *ft_r = &Friends.list[friendnumber].file_receiver[i]; + FileTransfer *ft_s = &Friends.list[friendnumber].file_sender[i]; refresh_progress_helper(self, ft_r); refresh_progress_helper(self, ft_s); @@ -140,9 +141,9 @@ bool refresh_file_transfer_progress(ToxWindow *self, uint32_t friendnumber) return active; } -static void clear_file_transfer(struct FileTransfer *ft) +static void clear_file_transfer(FileTransfer *ft) { - *ft = (struct FileTransfer) { + *ft = (FileTransfer) { 0 }; } @@ -150,16 +151,16 @@ static void clear_file_transfer(struct FileTransfer *ft) /* Returns a pointer to friendnumber's FileTransfer struct associated with filenumber. * Returns NULL if filenumber is invalid. */ -struct FileTransfer *get_file_transfer_struct(uint32_t friendnumber, uint32_t filenumber) +FileTransfer *get_file_transfer_struct(uint32_t friendnumber, uint32_t filenumber) { for (size_t i = 0; i < MAX_FILES; ++i) { - struct FileTransfer *ft_send = &Friends.list[friendnumber].file_sender[i]; + FileTransfer *ft_send = &Friends.list[friendnumber].file_sender[i]; if (ft_send->state != FILE_TRANSFER_INACTIVE && ft_send->filenumber == filenumber) { return ft_send; } - struct FileTransfer *ft_recv = &Friends.list[friendnumber].file_receiver[i]; + FileTransfer *ft_recv = &Friends.list[friendnumber].file_receiver[i]; if (ft_recv->state != FILE_TRANSFER_INACTIVE && ft_recv->filenumber == filenumber) { return ft_recv; @@ -172,7 +173,7 @@ struct FileTransfer *get_file_transfer_struct(uint32_t friendnumber, uint32_t fi /* Returns a pointer to the FileTransfer struct associated with index with the direction specified. * Returns NULL on failure. */ -struct FileTransfer *get_file_transfer_struct_index(uint32_t friendnumber, uint32_t index, +FileTransfer *get_file_transfer_struct_index(uint32_t friendnumber, uint32_t index, FILE_TRANSFER_DIRECTION direction) { if (direction != FILE_TRANSFER_RECV && direction != FILE_TRANSFER_SEND) { @@ -180,9 +181,9 @@ struct FileTransfer *get_file_transfer_struct_index(uint32_t friendnumber, uint3 } for (size_t i = 0; i < MAX_FILES; ++i) { - struct FileTransfer *ft = direction == FILE_TRANSFER_SEND ? - &Friends.list[friendnumber].file_sender[i] : - &Friends.list[friendnumber].file_receiver[i]; + FileTransfer *ft = direction == FILE_TRANSFER_SEND ? + &Friends.list[friendnumber].file_sender[i] : + &Friends.list[friendnumber].file_receiver[i]; if (ft->state != FILE_TRANSFER_INACTIVE && ft->index == index) { return ft; @@ -195,10 +196,10 @@ struct FileTransfer *get_file_transfer_struct_index(uint32_t friendnumber, uint3 /* Returns a pointer to an unused file sender. * Returns NULL if all file senders are in use. */ -static struct FileTransfer *new_file_sender(ToxWindow *window, uint32_t friendnumber, uint32_t filenumber, uint8_t type) +static FileTransfer *new_file_sender(ToxWindow *window, uint32_t friendnumber, uint32_t filenumber, uint8_t type) { for (size_t i = 0; i < MAX_FILES; ++i) { - struct FileTransfer *ft = &Friends.list[friendnumber].file_sender[i]; + FileTransfer *ft = &Friends.list[friendnumber].file_sender[i]; if (ft->state == FILE_TRANSFER_INACTIVE) { clear_file_transfer(ft); @@ -218,11 +219,11 @@ static struct FileTransfer *new_file_sender(ToxWindow *window, uint32_t friendnu /* Returns a pointer to an unused file receiver. * Returns NULL if all file receivers are in use. */ -static struct FileTransfer *new_file_receiver(ToxWindow *window, uint32_t friendnumber, uint32_t filenumber, - uint8_t type) +static FileTransfer *new_file_receiver(ToxWindow *window, uint32_t friendnumber, uint32_t filenumber, + uint8_t type) { for (size_t i = 0; i < MAX_FILES; ++i) { - struct FileTransfer *ft = &Friends.list[friendnumber].file_receiver[i]; + FileTransfer *ft = &Friends.list[friendnumber].file_receiver[i]; if (ft->state == FILE_TRANSFER_INACTIVE) { clear_file_transfer(ft); @@ -242,8 +243,8 @@ static struct FileTransfer *new_file_receiver(ToxWindow *window, uint32_t friend /* Initializes an unused file transfer and returns its pointer. * Returns NULL on failure. */ -struct FileTransfer *new_file_transfer(ToxWindow *window, uint32_t friendnumber, uint32_t filenumber, - FILE_TRANSFER_DIRECTION direction, uint8_t type) +FileTransfer *new_file_transfer(ToxWindow *window, uint32_t friendnumber, uint32_t filenumber, + FILE_TRANSFER_DIRECTION direction, uint8_t type) { if (direction == FILE_TRANSFER_RECV) { return new_file_receiver(window, friendnumber, filenumber, type); @@ -256,13 +257,83 @@ struct FileTransfer *new_file_transfer(ToxWindow *window, uint32_t friendnumber, return NULL; } +int file_send_queue_add(uint32_t friendnumber, const char *file_path, size_t length) +{ + if (length == 0 || file_path == NULL) { + return -1; + } + + if (length > TOX_MAX_FILENAME_LENGTH) { + return -2; + } + + for (size_t i = 0; i < MAX_FILES; ++i) { + PendingFileTransfer *pending_slot = &Friends.list[friendnumber].file_send_queue[i]; + + if (pending_slot->pending) { + continue; + } + + pending_slot->pending = true; + + memcpy(pending_slot->file_path, file_path, length); + pending_slot->file_path[length] = 0; + pending_slot->length = length; + + return i; + } + + return -3; +} + +#define FILE_TRANSFER_SEND_CMD "/sendfile " +#define FILE_TRANSFER_SEND_LEN (sizeof(FILE_TRANSFER_SEND_CMD) - 1) + +void file_send_queue_check(ToxWindow *self, Tox *m, uint32_t friendnumber) +{ + for (size_t i = 0; i < MAX_FILES; ++i) { + PendingFileTransfer *pending_slot = &Friends.list[friendnumber].file_send_queue[i]; + + if (!pending_slot->pending) { + continue; + } + + char command[TOX_MAX_FILENAME_LENGTH + FILE_TRANSFER_SEND_LEN + 1]; + snprintf(command, sizeof(command), "%s%s", FILE_TRANSFER_SEND_CMD, pending_slot->file_path); + + execute(self->window, self, m, command, CHAT_COMMAND_MODE); + + *pending_slot = (PendingFileTransfer) { + 0, + }; + } +} + +int file_send_queue_remove(uint32_t friendnumber, size_t index) +{ + if (index >= MAX_FILES) { + return -1; + } + + PendingFileTransfer *pending_slot = &Friends.list[friendnumber].file_send_queue[index]; + + if (!pending_slot->pending) { + return -1; + } + + *pending_slot = (PendingFileTransfer) { + 0, + }; + + return 0; +} /* Closes file transfer ft. * * Set CTRL to -1 if we don't want to send a control signal. * Set message or self to NULL if we don't want to display a message. */ -void close_file_transfer(ToxWindow *self, Tox *m, struct FileTransfer *ft, int CTRL, const char *message, +void close_file_transfer(ToxWindow *self, Tox *m, FileTransfer *ft, int CTRL, const char *message, Notification sound_type) { if (!ft) { @@ -298,7 +369,7 @@ void close_file_transfer(ToxWindow *self, Tox *m, struct FileTransfer *ft, int C void kill_avatar_file_transfers_friend(Tox *m, uint32_t friendnumber) { for (size_t i = 0; i < MAX_FILES; ++i) { - struct FileTransfer *ft = &Friends.list[friendnumber].file_sender[i]; + FileTransfer *ft = &Friends.list[friendnumber].file_sender[i]; if (ft->file_type == TOX_FILE_KIND_AVATAR) { close_file_transfer(NULL, m, ft, TOX_FILE_CONTROL_CANCEL, NULL, silent); @@ -312,6 +383,7 @@ void kill_all_file_transfers_friend(Tox *m, uint32_t friendnumber) for (size_t i = 0; i < MAX_FILES; ++i) { close_file_transfer(NULL, m, &Friends.list[friendnumber].file_sender[i], TOX_FILE_CONTROL_CANCEL, NULL, silent); close_file_transfer(NULL, m, &Friends.list[friendnumber].file_receiver[i], TOX_FILE_CONTROL_CANCEL, NULL, silent); + file_send_queue_remove(friendnumber, i); } } diff --git a/src/file_transfers.h b/src/file_transfers.h index dc2ac22..380378d 100644 --- a/src/file_transfers.h +++ b/src/file_transfers.h @@ -29,9 +29,9 @@ #include "toxic.h" #include "windows.h" -#define KiB 1024 -#define MiB 1048576 /* 1024^2 */ -#define GiB 1073741824 /* 1024^3 */ +#define KiB (uint32_t) 1024 +#define MiB (uint32_t) (1024 << 10) /* 1024^2 */ +#define GiB (uint32_t) (1024 << 20) /* 1024^3 */ #define MAX_FILES 32 @@ -47,7 +47,7 @@ typedef enum FILE_TRANSFER_DIRECTION { FILE_TRANSFER_RECV } FILE_TRANSFER_DIRECTION; -struct FileTransfer { +typedef struct FileTransfer { ToxWindow *window; FILE *file; FILE_TRANSFER_STATE state; @@ -63,7 +63,15 @@ struct FileTransfer { time_t last_line_progress; /* The last time we updated the progress bar */ uint32_t line_id; uint8_t file_id[TOX_FILE_ID_LENGTH]; -}; +} FileTransfer; + +typedef struct PendingFileTransfer { + char file_path[TOX_MAX_FILENAME_LENGTH + 1]; + size_t length; + uint32_t friendnumber; + bool pending; +} PendingFileTransfer; + /* creates initial progress line that will be updated during file transfer. progline must be at lesat MAX_STR_SIZE bytes */ @@ -96,6 +104,31 @@ struct FileTransfer *get_file_transfer_struct_index(uint32_t friendnumber, uint3 struct FileTransfer *new_file_transfer(ToxWindow *window, uint32_t friendnumber, uint32_t filenumber, FILE_TRANSFER_DIRECTION direction, uint8_t type); +/* Adds a file designated by `file_path` of length `length` to the file transfer queue. + * + * Items in this queue will be automatically sent to the contact designated by `friendnumber` + * as soon as they appear online. The item will then be removed from the queue whether + * or not the transfer successfully initiates. + * + * If the ToxWindow associated with this friend is closed, all queued items will be + * discarded. + * + * Return the queue index on success. + * Return -1 if the length is invalid. + * Return -2 if the send queue is full. + */ +int file_send_queue_add(uint32_t friendnumber, const char *file_path, size_t length); + +/* Initiates all file transfers from the file send queue for friend designated by `friendnumber`. */ +void file_send_queue_check(ToxWindow *self, Tox *m, uint32_t friendnumber); + +/* Removes the `index`-th item from the file send queue for `friendnumber`. + * + * Return 0 if a pending transfer was successfully removed + * Return -1 if index does not designate a pending file transfer. + */ +int file_send_queue_remove(uint32_t friendnumber, size_t index); + /* Closes file transfer ft. * * Set CTRL to -1 if we don't want to send a control signal. diff --git a/src/friendlist.c b/src/friendlist.c index 80a7202..6f9c6fd 100644 --- a/src/friendlist.c +++ b/src/friendlist.c @@ -1107,14 +1107,14 @@ static void friendlist_onDraw(ToxWindow *self, Tox *m) /* Determine which portion of friendlist to draw based on current position */ pthread_mutex_lock(&Winthread.lock); - int page = Friends.num_selected / (y2 - FLIST_OFST); + const int page = Friends.num_selected / (y2 - FLIST_OFST); pthread_mutex_unlock(&Winthread.lock); - int start = (y2 - FLIST_OFST) * page; - int end = y2 - FLIST_OFST + start; + const int start = (y2 - FLIST_OFST) * page; + const int end = y2 - FLIST_OFST + start; pthread_mutex_lock(&Winthread.lock); - size_t num_friends = Friends.num_friends; + const size_t num_friends = Friends.num_friends; pthread_mutex_unlock(&Winthread.lock); int i; diff --git a/src/friendlist.h b/src/friendlist.h index 694f7ef..f537bdc 100644 --- a/src/friendlist.h +++ b/src/friendlist.h @@ -79,8 +79,9 @@ typedef struct { struct GameInvite game_invite; #endif - struct FileTransfer file_receiver[MAX_FILES]; - struct FileTransfer file_sender[MAX_FILES]; + FileTransfer file_receiver[MAX_FILES]; + FileTransfer file_sender[MAX_FILES]; + PendingFileTransfer file_send_queue[MAX_FILES]; } ToxicFriend; typedef struct { diff --git a/src/windows.c b/src/windows.c index 37d3a10..6ffa41f 100644 --- a/src/windows.c +++ b/src/windows.c @@ -244,7 +244,7 @@ void on_file_chunk_request(Tox *m, uint32_t friendnumber, uint32_t filenumber, u { UNUSED_VAR(userdata); - struct FileTransfer *ft = get_file_transfer_struct(friendnumber, filenumber); + FileTransfer *ft = get_file_transfer_struct(friendnumber, filenumber); if (!ft) { return; @@ -267,7 +267,7 @@ void on_file_recv_chunk(Tox *m, uint32_t friendnumber, uint32_t filenumber, uint { UNUSED_VAR(userdata); - struct FileTransfer *ft = get_file_transfer_struct(friendnumber, filenumber); + FileTransfer *ft = get_file_transfer_struct(friendnumber, filenumber); if (!ft) { return; @@ -285,7 +285,7 @@ void on_file_recv_control(Tox *m, uint32_t friendnumber, uint32_t filenumber, To { UNUSED_VAR(userdata); - struct FileTransfer *ft = get_file_transfer_struct(friendnumber, filenumber); + FileTransfer *ft = get_file_transfer_struct(friendnumber, filenumber); if (!ft) { return;