Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7711749d03
|
||
|
|
5b42ea6a73
|
||
|
|
92115c0dd4
|
||
|
|
62626aaea9
|
||
|
|
a2568443e0 |
@@ -0,0 +1,6 @@
|
||||
# [submodule "external/Unity"]
|
||||
# path = external/Unity
|
||||
# url = https://github.com/ThrowTheSwitch/Unity.git
|
||||
[submodule "external/cmocka"]
|
||||
path = external/cmocka
|
||||
url = https://git.cryptomilk.org/projects/cmocka.git
|
||||
@@ -23,9 +23,16 @@ add_subdirectory(src/modals)
|
||||
add_subdirectory(src/lib)
|
||||
add_subdirectory(src/utils)
|
||||
add_subdirectory(src/windows)
|
||||
add_subdirectory(src/core)
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
# Testing
|
||||
include(CTest)
|
||||
include(cmake/OptionsCMocka.cmake)
|
||||
add_subdirectory(external/cmocka)
|
||||
add_subdirectory(test)
|
||||
|
||||
install(
|
||||
TARGETS zbbs-client-tui
|
||||
)
|
||||
@@ -1,40 +1,20 @@
|
||||
# TODO:
|
||||
|
||||
## Current:
|
||||
- [ ] File upload multiline description (window)
|
||||
- [ ] Sorting in the file list
|
||||
- [ ] Resume download
|
||||
- [ ] Separate common structures in the common.h
|
||||
- [x] Rewrite cancel download. Use packages and check every time for a confirmation to continue download
|
||||
- [x] Show download/upload progress in percentages
|
||||
- [x] Break line automatically in modal widgets
|
||||
- [x] Cancel upload/download
|
||||
- [x] Input: fix non-Latin letters
|
||||
- [x] Get rid of unsigned integers
|
||||
- [x] Check registration / enter server name again
|
||||
- [x] PageUp/PageDown key functionality in lists
|
||||
- [x] Permission checkboxes for file upload/edit
|
||||
- [x] Upload menu disappears after alert (when opening link to non-existing directory for example).
|
||||
- [ ] Sorting in the file list (local files):
|
||||
- [ ] Testing env
|
||||
- [ ] Test dlist
|
||||
- [ ] Dlist sort
|
||||
|
||||
## Global tasks:
|
||||
- [ ] Testing
|
||||
- [ ] Documentation
|
||||
|
||||
## Bugs:
|
||||
- [x] Sometimes segfaults while navigating through modal menu
|
||||
- [ ] Resume download
|
||||
- [ ] Separate common structures in the common.h
|
||||
|
||||
## Features:
|
||||
- [x] Search files
|
||||
- [x] Correct spacing in the search bar
|
||||
- [x] Finish registration
|
||||
- [x] Privilege settings for files
|
||||
- [ ] Files removal
|
||||
- [ ] Encryption
|
||||
- [x] UTF-8 support
|
||||
- [ ] Check MD5 sum after download/upload
|
||||
- [ ] Hotkeys for buttons
|
||||
- [ ] Manipulation by UI-cursor
|
||||
|
||||
## Fixes:
|
||||
- [x] Check and fix registration and login
|
||||
- [x] CMAKE
|
||||
@@ -0,0 +1,7 @@
|
||||
# Disable things we don't need from CMocka
|
||||
set(WITH_EXAMPLES OFF CACHE BOOL "Build examples" FORCE)
|
||||
set(UNIT_TESTING OFF CACHE BOOL "Build CMocka's own tests" FORCE)
|
||||
set(WITH_CMOCKERY_SUPPORT OFF CACHE BOOL "" FORCE)
|
||||
|
||||
# Force static library (easier for most projects)
|
||||
set(WITH_STATIC_LIB ON CACHE BOOL "Build static library" FORCE)
|
||||
+1
Submodule external/cmocka added at 7f736f65dc
+12
-7
@@ -20,17 +20,21 @@ elseif(
|
||||
endif()
|
||||
endif()
|
||||
|
||||
file(GLOB M_SOURCES "*.c")
|
||||
|
||||
if(DEV_WIDGET)
|
||||
list(FILTER M_SOURCES EXCLUDE REGEX "main\\.c$")
|
||||
target_sources(zbbs-client-tui
|
||||
PRIVATE
|
||||
main_dev_widget.c
|
||||
)
|
||||
else()
|
||||
list(FILTER M_SOURCES EXCLUDE REGEX "main_dev_widget\\.c$")
|
||||
target_sources(zbbs-client-tui
|
||||
PRIVATE
|
||||
main.c
|
||||
)
|
||||
endif()
|
||||
|
||||
target_sources(zbbs-client-tui
|
||||
PRIVATE
|
||||
${M_SOURCES}
|
||||
add_library(common INTERFACE)
|
||||
target_sources(common
|
||||
PUBLIC FILE_SET HEADERS FILES common.h
|
||||
)
|
||||
|
||||
target_link_libraries(zbbs-client-tui
|
||||
@@ -41,5 +45,6 @@ target_link_libraries(zbbs-client-tui
|
||||
bstring
|
||||
windows
|
||||
utils
|
||||
common
|
||||
${CURSES_LIBRARIES}
|
||||
)
|
||||
+280
@@ -4,8 +4,22 @@
|
||||
#ifndef COMMON_H
|
||||
#define COMMON_H
|
||||
|
||||
#include <d_array.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define LOCAL_PORT 1999
|
||||
#define SERVER_PORT 2000
|
||||
#define LISTEN_QLEN 32
|
||||
#define INBUFSIZE 1024
|
||||
#define DOWNLOADS_DIR "./Downloads"
|
||||
#define DIALOGUE_TITLE 64
|
||||
#define DIALOGUE_TEXT 4096
|
||||
#define INPUT_TEXT DIALOGUE_TITLE
|
||||
#define PACKAGE_SIZE INBUFSIZE * 10
|
||||
|
||||
typedef struct {
|
||||
int32_t cur_y, cur_x;
|
||||
int32_t max_y, max_x;
|
||||
@@ -25,4 +39,270 @@ struct action_key {
|
||||
int (*callback)(void *app, void *d_args);
|
||||
};
|
||||
|
||||
typedef struct fl_item {
|
||||
size_t size;
|
||||
char *name;
|
||||
char *description;
|
||||
char *owner;
|
||||
struct fl_item *next;
|
||||
} fl_item_t;
|
||||
|
||||
enum main_window_type {
|
||||
mw_fl_server, /* file list on the server */
|
||||
mw_fl_local, /* local file list */
|
||||
mw_f_desc, /* file description */
|
||||
};
|
||||
|
||||
typedef void(draw_f_t)(void *fui);
|
||||
|
||||
typedef struct main_window {
|
||||
enum main_window_type type;
|
||||
void *ui; /* structure which describes window UI */
|
||||
int32_t (*cb_b_press)(void *app, void *args); /* callback on button press */
|
||||
void (*cb_ui_refresh)(void *app); /* refresh callback */
|
||||
void (*reset)(void *app); /* function to reset the state of the window */
|
||||
draw_f_t *draw;
|
||||
struct action_key *b_keys;
|
||||
int32_t b_keys_len;
|
||||
} main_window_t;
|
||||
|
||||
|
||||
typedef struct file_args {
|
||||
int file_d;
|
||||
fl_item_t f_selected;
|
||||
fl_item_t *l_start;
|
||||
fl_item_t *l_current;
|
||||
} file_args_t;
|
||||
|
||||
|
||||
typedef struct params {
|
||||
unsigned short port;
|
||||
unsigned addr;
|
||||
char *uname;
|
||||
char *pass;
|
||||
char privileges;
|
||||
int32_t sd;
|
||||
int32_t is_connected : 1;
|
||||
} params_t;
|
||||
|
||||
enum state {
|
||||
/* TUI-based */
|
||||
S_N_D,
|
||||
S_ASK_SEVER_IP,
|
||||
S_ASK_LOGIN_TYPE,
|
||||
S_ASK_LOGIN_USER,
|
||||
S_ASK_REGISTER,
|
||||
S_WAIT_SERVER,
|
||||
S_PREP_SERVER_MESSAGE,
|
||||
S_PRINT_SERVER_MESSAGE,
|
||||
S_FILE_LIST,
|
||||
S_WAIT_PASS,
|
||||
/* S_FILE_SELECT, */
|
||||
S_DOWNLOAD_FILE,
|
||||
S_UPLOAD_PARAMS,
|
||||
S_UPLOAD_FILE,
|
||||
/* S_UPLOAD_FILE_SELECT, */
|
||||
S_UPLOAD_REQUESTED,
|
||||
S_UPLOAD_SERVER_FINISHES,
|
||||
S_ASK_USER_BEFORE_LOGIN,
|
||||
S_ERR,
|
||||
S_NEXT_ACTION,
|
||||
S_WAIT_REGISTER_CONFIRMATION,
|
||||
S_WAIT_USER_DESCRIPTION,
|
||||
/* Legacy */
|
||||
WAIT_SERVER_INIT,
|
||||
WAIT_SERVER,
|
||||
WAIT_REGISTER,
|
||||
WAIT_REGISTER_CONFIRMATION,
|
||||
WAIT_CLIENT,
|
||||
};
|
||||
|
||||
enum package_signal { sig_continue, sig_cancel, sig_finish, sig_pause };
|
||||
|
||||
typedef struct {
|
||||
enum package_signal signal;
|
||||
size_t package_size;
|
||||
} s_file_pd_t; /* file package descriptor */
|
||||
|
||||
typedef struct p_file {
|
||||
char *name;
|
||||
char *path;
|
||||
char *description;
|
||||
size_t size;
|
||||
size_t rest;
|
||||
size_t package_rest;
|
||||
int32_t it_count;
|
||||
size_t it_interval;
|
||||
enum package_signal signal;
|
||||
int fd;
|
||||
} p_file_t;
|
||||
|
||||
typedef struct query_args {
|
||||
int32_t sd;
|
||||
int32_t buf_used;
|
||||
p_file_t *file;
|
||||
enum state state;
|
||||
char buf[INBUFSIZE];
|
||||
bool from_server;
|
||||
params_t *params;
|
||||
struct {
|
||||
char *text;
|
||||
int32_t capacity;
|
||||
int32_t size;
|
||||
} server_message;
|
||||
main_window_t *main_ui;
|
||||
void *progress_bar;
|
||||
void *active_dialogue;
|
||||
void *notification;
|
||||
} query_args_t;
|
||||
|
||||
enum w_dialogue_color_scheme { dc_normal, dc_alert };
|
||||
|
||||
enum w_g_type { g_content, g_action };
|
||||
|
||||
enum w_g_dir {
|
||||
horizontal,
|
||||
vertical,
|
||||
};
|
||||
|
||||
enum w_type {
|
||||
w_end,
|
||||
w_button,
|
||||
w_box,
|
||||
w_group,
|
||||
w_input,
|
||||
w_checkbox,
|
||||
w_progress,
|
||||
};
|
||||
|
||||
enum w_val_type {
|
||||
val_num,
|
||||
val_ptr,
|
||||
val_nul,
|
||||
};
|
||||
|
||||
struct w_val_t {
|
||||
enum w_val_type type;
|
||||
union {
|
||||
void *ptr;
|
||||
int32_t num;
|
||||
} val;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int32_t id;
|
||||
int32_t idx; /* element index in the group */
|
||||
void *element;
|
||||
enum w_type type;
|
||||
enum w_g_type g_type;
|
||||
bool is_default;
|
||||
struct w_val_t val;
|
||||
} w_g_el_t;
|
||||
|
||||
/* widget callback response code */
|
||||
enum w_cbrp_code {
|
||||
cbrc_none, /* default */
|
||||
cbrp_val,
|
||||
cbrp_g_el, /* element of type group_el_t */
|
||||
cbrp_err
|
||||
};
|
||||
|
||||
enum w_pmt_attrs {
|
||||
PMT_POS_CENTER = 01,
|
||||
PMT_ALIGN_CENTER = 03,
|
||||
};
|
||||
|
||||
/* widget callback response data */
|
||||
typedef struct {
|
||||
enum w_cbrp_code code;
|
||||
struct w_val_t val;
|
||||
} w_cbrp_data;
|
||||
|
||||
typedef struct {
|
||||
void *app;
|
||||
void *element;
|
||||
void *data;
|
||||
w_cbrp_data resp_data;
|
||||
void *active_el;
|
||||
} w_cb_args_t;
|
||||
|
||||
typedef int32_t (*w_cb_press_t)(void *app, void *data);
|
||||
|
||||
typedef void (*w_cb_t)(w_cb_args_t *args);
|
||||
|
||||
typedef struct w_t {
|
||||
int32_t id;
|
||||
char title[DIALOGUE_TITLE];
|
||||
struct {
|
||||
int32_t y;
|
||||
int32_t x;
|
||||
} cur; /* cursor position */
|
||||
struct {
|
||||
int32_t y;
|
||||
int32_t x;
|
||||
} sz; /* size */
|
||||
struct {
|
||||
int32_t y;
|
||||
int32_t x;
|
||||
} m; /* margin (relative to the parent) */
|
||||
struct {
|
||||
int32_t y;
|
||||
int32_t x;
|
||||
} ps; /* position (relative to the window) */
|
||||
WINDOW *const *parent_win;
|
||||
struct w_t *w_parent;
|
||||
w_cb_t callback;
|
||||
} w_t;
|
||||
|
||||
typedef struct group_el_init_t {
|
||||
enum w_type type;
|
||||
bool is_default;
|
||||
bool is_hidden_value;
|
||||
char label[DIALOGUE_TITLE];
|
||||
int32_t length;
|
||||
enum w_g_dir direction;
|
||||
union {
|
||||
int32_t num;
|
||||
} val;
|
||||
struct group_el_init_t *children;
|
||||
} w_group_el_init_t;
|
||||
|
||||
typedef struct group_t {
|
||||
w_t w;
|
||||
w_g_el_t *elements;
|
||||
enum w_g_dir direction;
|
||||
int32_t count;
|
||||
int32_t first_id, last_id;
|
||||
struct group_t *parent_group;
|
||||
} w_group_t;
|
||||
|
||||
typedef struct {
|
||||
w_t w;
|
||||
WINDOW *win;
|
||||
w_group_t *g_content;
|
||||
w_group_t *g_action;
|
||||
w_g_el_t *active_el;
|
||||
char text[DIALOGUE_TEXT];
|
||||
bool is_initiated;
|
||||
bool needs_update;
|
||||
bool needs_destroy;
|
||||
coordinates_t *p_coordinates;
|
||||
enum w_dialogue_color_scheme color_scheme;
|
||||
d_array_ptr_t id_map;
|
||||
} w_dialogue_t;
|
||||
|
||||
typedef struct app_t {
|
||||
WINDOW *win;
|
||||
w_dialogue_t modal;
|
||||
params_t *params;
|
||||
coordinates_t coordinates;
|
||||
query_args_t *query_args;
|
||||
file_args_t *file_args;
|
||||
void (*active_callback)(w_cb_args_t *args);
|
||||
void (*callback_after_notification)(struct app_t *);
|
||||
void *active_widget;
|
||||
wchar_t *top_text;
|
||||
main_window_t main_ui;
|
||||
} app_t;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
add_library(core STATIC)
|
||||
|
||||
file(GLOB M_SOURCES "*.c")
|
||||
|
||||
target_sources(core
|
||||
PRIVATE
|
||||
${M_SOURCES}
|
||||
PUBLIC
|
||||
FILE_SET HEADERS
|
||||
FILES
|
||||
core.h
|
||||
)
|
||||
target_link_libraries(core
|
||||
PRIVATE
|
||||
common
|
||||
widget_core
|
||||
widget
|
||||
bstring
|
||||
utils
|
||||
modals
|
||||
windows
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include "../app.h"
|
||||
#include "app.h"
|
||||
#include <locale.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <netinet/in.h>
|
||||
@@ -4,9 +4,7 @@
|
||||
#ifndef APP_H
|
||||
#define APP_H
|
||||
|
||||
#include "../main.h"
|
||||
#include "../common.h"
|
||||
#include "dialogue.h"
|
||||
#include <common.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <widget_core.h>
|
||||
#include <bstrlib.h>
|
||||
@@ -15,20 +13,6 @@ typedef enum {
|
||||
modal_color_pair = 3,
|
||||
} app_color;
|
||||
|
||||
typedef struct app_t {
|
||||
WINDOW *win;
|
||||
w_dialogue_t modal;
|
||||
params_t *params;
|
||||
coordinates_t coordinates;
|
||||
query_args_t *query_args;
|
||||
file_args_t *file_args;
|
||||
void (*active_callback)(w_cb_args_t *args);
|
||||
void (*callback_after_notification)(struct app_t *);
|
||||
void *active_widget;
|
||||
wchar_t *top_text;
|
||||
main_window_t main_ui;
|
||||
} app_t;
|
||||
|
||||
app_t *app_init(app_t *app);
|
||||
void app_init_nc(void);
|
||||
void app_destroy(app_t *app, int32_t exit_code);
|
||||
@@ -1,8 +1,8 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include "alert.h"
|
||||
#include "main.h"
|
||||
#include "app.h"
|
||||
#include <widget.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#ifndef CONNECTION_H
|
||||
#define CONNECTION_H
|
||||
#include "main.h"
|
||||
|
||||
#include <widget.h>
|
||||
|
||||
bool connect_to_server(app_t *params);
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef CORE_H
|
||||
#define CORE_H
|
||||
|
||||
#include "app.h"
|
||||
#include "connection.h"
|
||||
#include "draw_modal.h"
|
||||
#include "file_processor.h"
|
||||
#include "fs.h"
|
||||
#include "query.h"
|
||||
#include "server.h"
|
||||
|
||||
#endif
|
||||
@@ -1,7 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include "main.h"
|
||||
#include <modals.h>
|
||||
#include <widget.h>
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#ifndef DRAW_MODAL_H
|
||||
#define DRAW_MODAL_H
|
||||
|
||||
#include <widget.h>
|
||||
|
||||
void app_draw_modal(app_t *app);
|
||||
#endif
|
||||
@@ -2,7 +2,6 @@
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include "alert.h"
|
||||
#include "dialogue.h"
|
||||
#include "server.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
@@ -20,10 +19,10 @@
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <widget.h>
|
||||
#include <windows.h>
|
||||
#include <common.h>
|
||||
#include <windows/file_list.h>
|
||||
|
||||
#include "file_processor.h"
|
||||
#include "main.h"
|
||||
#include "query.h"
|
||||
|
||||
static void fl_add(fl_item_t **cur, fl_item_t **start, char *fname);
|
||||
@@ -4,7 +4,7 @@
|
||||
#ifndef FILE_PROCESSOR_H
|
||||
#define FILE_PROCESSOR_H
|
||||
|
||||
#include "main.h"
|
||||
#include "common.h"
|
||||
|
||||
void file_list(file_args_t *f_args, query_args_t *q_args);
|
||||
void file_download(file_args_t *f_args, query_args_t *q_args);
|
||||
@@ -16,4 +16,5 @@ int32_t file_upload_open(char *dpath, char *fname, query_args_t *q_args);
|
||||
void fl_clear(fl_item_t **start, fl_item_t **arg_current);
|
||||
int32_t ui_file_select(file_args_t *f_args, query_args_t *q_args, int32_t idx);
|
||||
void clear_file_in_query(query_args_t *q_args);
|
||||
|
||||
#endif
|
||||
@@ -1,7 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include "query.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
@@ -23,13 +22,12 @@
|
||||
#include <modals.h>
|
||||
#include <widget.h>
|
||||
#include <widget_core.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "query.h"
|
||||
#include "alert.h"
|
||||
#include "connection.h"
|
||||
#include "dialogue.h"
|
||||
#include "file_processor.h"
|
||||
#include "main.h"
|
||||
#include "main_window.h"
|
||||
#include "server.h"
|
||||
#include "types.h"
|
||||
@@ -4,8 +4,6 @@
|
||||
#ifndef QUERY_H
|
||||
#define QUERY_H
|
||||
|
||||
#include "../app.h"
|
||||
#include "main.h"
|
||||
#include <widget.h>
|
||||
|
||||
void query_loop(app_t *);
|
||||
@@ -1,9 +1,8 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include "../app.h"
|
||||
#include "app.h"
|
||||
#include "dialogue.h"
|
||||
#include "main.h"
|
||||
#include "query.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
@@ -18,7 +17,7 @@
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <widget.h>
|
||||
#include <windows.h>
|
||||
#include <windows/file_list.h>
|
||||
|
||||
void ask_uname_and_password(params_t *params);
|
||||
void ask_register(params_t *params, char *email);
|
||||
@@ -4,8 +4,8 @@
|
||||
#ifndef SERVER_H
|
||||
#define SERVER_H
|
||||
|
||||
#include "../app.h"
|
||||
#include "main.h"
|
||||
#include <common.h>
|
||||
|
||||
int process_server_command(char *line, int l_len, app_t *app);
|
||||
void ask_register(params_t *params, char *email);
|
||||
void server_send_string(query_args_t *q, const char *fmt, ...);
|
||||
+3
-5
@@ -14,12 +14,10 @@
|
||||
#include <widget.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "../app.h"
|
||||
#include <core/app.h>
|
||||
#include "alert.h"
|
||||
#include "connection.h"
|
||||
// #include "file_list.h"
|
||||
#include "main.h"
|
||||
#include "query.h"
|
||||
#include <core/connection.h>
|
||||
#include <core/query.h>
|
||||
|
||||
int32_t m_id = 0;
|
||||
|
||||
|
||||
-125
@@ -9,129 +9,4 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define LOCAL_PORT 1999
|
||||
#define SERVER_PORT 2000
|
||||
#define LISTEN_QLEN 32
|
||||
#define INBUFSIZE 1024
|
||||
#define DOWNLOADS_DIR "./Downloads"
|
||||
#define DIALOGUE_TITLE 64
|
||||
#define DIALOGUE_TEXT 4096
|
||||
#define INPUT_TEXT DIALOGUE_TITLE
|
||||
#define PACKAGE_SIZE INBUFSIZE * 10
|
||||
|
||||
typedef struct params {
|
||||
unsigned short port;
|
||||
unsigned addr;
|
||||
char *uname;
|
||||
char *pass;
|
||||
char privileges;
|
||||
int32_t sd;
|
||||
int32_t is_connected : 1;
|
||||
} params_t;
|
||||
|
||||
enum state {
|
||||
/* TUI-based */
|
||||
S_N_D,
|
||||
S_ASK_SEVER_IP,
|
||||
S_ASK_LOGIN_TYPE,
|
||||
S_ASK_LOGIN_USER,
|
||||
S_ASK_REGISTER,
|
||||
S_WAIT_SERVER,
|
||||
S_PREP_SERVER_MESSAGE,
|
||||
S_PRINT_SERVER_MESSAGE,
|
||||
S_FILE_LIST,
|
||||
S_WAIT_PASS,
|
||||
/* S_FILE_SELECT, */
|
||||
S_DOWNLOAD_FILE,
|
||||
S_UPLOAD_PARAMS,
|
||||
S_UPLOAD_FILE,
|
||||
/* S_UPLOAD_FILE_SELECT, */
|
||||
S_UPLOAD_REQUESTED,
|
||||
S_UPLOAD_SERVER_FINISHES,
|
||||
S_ASK_USER_BEFORE_LOGIN,
|
||||
S_ERR,
|
||||
S_NEXT_ACTION,
|
||||
S_WAIT_REGISTER_CONFIRMATION,
|
||||
S_WAIT_USER_DESCRIPTION,
|
||||
/* Legacy */
|
||||
WAIT_SERVER_INIT,
|
||||
WAIT_SERVER,
|
||||
WAIT_REGISTER,
|
||||
WAIT_REGISTER_CONFIRMATION,
|
||||
WAIT_CLIENT,
|
||||
};
|
||||
|
||||
enum package_signal { sig_continue, sig_cancel, sig_finish, sig_pause };
|
||||
|
||||
typedef struct {
|
||||
enum package_signal signal;
|
||||
size_t package_size;
|
||||
} s_file_pd_t; /* file package descriptor */
|
||||
|
||||
typedef struct p_file {
|
||||
char *name;
|
||||
char *path;
|
||||
char *description;
|
||||
size_t size;
|
||||
size_t rest;
|
||||
size_t package_rest;
|
||||
int32_t it_count;
|
||||
size_t it_interval;
|
||||
enum package_signal signal;
|
||||
int fd;
|
||||
} p_file_t;
|
||||
|
||||
typedef struct fl_item {
|
||||
size_t size;
|
||||
char *name;
|
||||
char *description;
|
||||
char *owner;
|
||||
struct fl_item *next;
|
||||
} fl_item_t;
|
||||
|
||||
typedef struct file_args {
|
||||
int file_d;
|
||||
fl_item_t f_selected;
|
||||
fl_item_t *l_start;
|
||||
fl_item_t *l_current;
|
||||
} file_args_t;
|
||||
|
||||
enum main_window_type {
|
||||
mw_fl_server, /* file list on the server */
|
||||
mw_fl_local, /* local file list */
|
||||
mw_f_desc, /* file description */
|
||||
};
|
||||
|
||||
typedef void(draw_f_t)(void *fui);
|
||||
|
||||
typedef struct main_window {
|
||||
enum main_window_type type;
|
||||
void *ui; /* structure which describes window UI */
|
||||
int32_t (*cb_b_press)(void *app, void *args); /* callback on button press */
|
||||
void (*cb_ui_refresh)(void *app); /* refresh callback */
|
||||
void (*reset)(void *app); /* function to reset the state of the window */
|
||||
draw_f_t *draw;
|
||||
struct action_key *b_keys;
|
||||
int32_t b_keys_len;
|
||||
} main_window_t;
|
||||
|
||||
typedef struct query_args {
|
||||
int32_t sd;
|
||||
int32_t buf_used;
|
||||
p_file_t *file;
|
||||
enum state state;
|
||||
char buf[INBUFSIZE];
|
||||
bool from_server;
|
||||
params_t *params;
|
||||
struct {
|
||||
char *text;
|
||||
int32_t capacity;
|
||||
int32_t size;
|
||||
} server_message;
|
||||
main_window_t *main_ui;
|
||||
void *progress_bar;
|
||||
void *active_dialogue;
|
||||
void *notification;
|
||||
} query_args_t;
|
||||
|
||||
#endif
|
||||
@@ -2,13 +2,13 @@ add_library(modals STATIC)
|
||||
|
||||
file(GLOB M_SOURCES "*.c")
|
||||
|
||||
target_link_directories(modals
|
||||
PRIVATE
|
||||
"../widget/"
|
||||
"../widget_core/"
|
||||
"../lib/"
|
||||
"../utils/"
|
||||
)
|
||||
# target_link_directories(modals
|
||||
# PRIVATE
|
||||
# "../widget/"
|
||||
# "../widget_core/"
|
||||
# "../lib/"
|
||||
# "../utils/"
|
||||
# )
|
||||
|
||||
target_link_libraries(modals
|
||||
PRIVATE
|
||||
@@ -16,6 +16,8 @@ target_link_libraries(modals
|
||||
widget_core
|
||||
bstring
|
||||
utils
|
||||
common
|
||||
windows
|
||||
)
|
||||
|
||||
target_sources(modals
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <widget.h>
|
||||
#include <widget_core.h>
|
||||
#include "../connection.h"
|
||||
#include <core/connection.h>
|
||||
#include <core/app.h>
|
||||
#include "group.h"
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
#include "../server.h"
|
||||
#include "../windows/file_list.h"
|
||||
#include "alert.h"
|
||||
#include "dialogue.h"
|
||||
#include "group.h"
|
||||
#include "utils.h"
|
||||
#include <core/server.h>
|
||||
#include <windows/file_list.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <widget.h>
|
||||
|
||||
#include "alert.h"
|
||||
#include "dialogue.h"
|
||||
#include "group.h"
|
||||
|
||||
void dwn_pr_modal_cb(w_cb_args_t *args) {
|
||||
/* only cancel action */
|
||||
int32_t key = *((int32_t *)args->data);
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include <widget.h>
|
||||
#include "../server.h"
|
||||
#include <core/server.h>
|
||||
#include <core/app.h>
|
||||
|
||||
typedef struct {
|
||||
w_button_t *element;
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
#include <unistd.h>
|
||||
#include <widget.h>
|
||||
#include "../server.h"
|
||||
#include <core/app.h>
|
||||
#include <core/server.h>
|
||||
|
||||
typedef struct {
|
||||
w_button_t *element;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "alert.h"
|
||||
#include "group.h"
|
||||
#include "../server.h"
|
||||
#include <core/server.h>
|
||||
|
||||
typedef struct {
|
||||
w_button_t *element;
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
#include "checkbox.h"
|
||||
#include "group.h"
|
||||
#include "widget_core.h"
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <utils.h>
|
||||
#include <widget.h>
|
||||
|
||||
#include "../server.h"
|
||||
#include <core/server.h>
|
||||
#include "checkbox.h"
|
||||
#include "group.h"
|
||||
|
||||
void upload_props_dialogue_modal_cb(w_cb_args_t *args) {
|
||||
w_cb_args_t d_args;
|
||||
@@ -40,10 +39,6 @@ void upload_props_dialogue_modal_cb(w_cb_args_t *args) {
|
||||
app->query_args->file->size, privileges);
|
||||
/* app->query_args->file->description =
|
||||
malloc(desc_input->value_len + 9); reserve space for \n:END:\n */
|
||||
app->query_args->file->description =
|
||||
malloc(sizeof("[Empty description]\n:END:\n"));
|
||||
strcpy(app->query_args->file->description,
|
||||
"[Empty description]\n:END:\n");
|
||||
d->needs_destroy = true;
|
||||
app->query_args->state = S_UPLOAD_REQUESTED;
|
||||
break;
|
||||
|
||||
@@ -7,6 +7,8 @@ target_link_libraries(widget
|
||||
bstring
|
||||
widget_core
|
||||
utils
|
||||
common
|
||||
core
|
||||
)
|
||||
|
||||
target_sources(widget
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#include "alert.h"
|
||||
#include "dialogue.h"
|
||||
#include "widget_core.h"
|
||||
#include <core/app.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
|
||||
+2
-1
@@ -1,8 +1,9 @@
|
||||
#ifndef W_ALERT_H
|
||||
#define W_ALERT_H
|
||||
|
||||
#include "../app.h"
|
||||
#include <common.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
void w_alert_init(app_t *app);
|
||||
void w_alert(const char *message);
|
||||
|
||||
+2
-1
@@ -8,7 +8,8 @@
|
||||
|
||||
#include <widget_core.h>
|
||||
#include "button.h"
|
||||
#include "../app.h"
|
||||
|
||||
#include <core/app.h>
|
||||
|
||||
w_button_t *init_button(WINDOW **win, w_t *w_parent, char *label) {
|
||||
w_button_t *btn = malloc(sizeof(w_button_t));
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../app.h"
|
||||
#include <core/app.h>
|
||||
#include "checkbox.h"
|
||||
#include <widget_core.h>
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <wchar.h>
|
||||
#include <widget.h>
|
||||
#include <widget_core.h>
|
||||
#include <core/app.h>
|
||||
|
||||
static void (*active_callback_backup)(w_cb_args_t *) = NULL;
|
||||
|
||||
|
||||
+2
-19
@@ -4,31 +4,14 @@
|
||||
#ifndef W_DIALOGUE_H
|
||||
#define W_DIALOGUE_H
|
||||
|
||||
#include "../common.h"
|
||||
#include "group.h"
|
||||
#include <common.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stdint.h>
|
||||
#include <utils.h>
|
||||
#include <widget_core.h>
|
||||
|
||||
#define MAX_IDS 64
|
||||
|
||||
enum w_dialogue_color_scheme { dc_normal, dc_alert };
|
||||
|
||||
typedef struct {
|
||||
w_t w;
|
||||
WINDOW *win;
|
||||
w_group_t *g_content;
|
||||
w_group_t *g_action;
|
||||
w_g_el_t *active_el;
|
||||
char text[DIALOGUE_TEXT];
|
||||
bool is_initiated;
|
||||
bool needs_update;
|
||||
bool needs_destroy;
|
||||
coordinates_t *p_coordinates;
|
||||
enum w_dialogue_color_scheme color_scheme;
|
||||
d_array_ptr_t id_map;
|
||||
} w_dialogue_t;
|
||||
|
||||
void w_dialogue_init(w_dialogue_t *d, const char title[], const char text[],
|
||||
coordinates_t *p_coordinates);
|
||||
void w_dialogue_vinit(w_dialogue_t *dialogue, const char title[],
|
||||
|
||||
+1
-22
@@ -7,28 +7,7 @@
|
||||
#include <stdint.h>
|
||||
#include <utils.h>
|
||||
#include <widget_core.h>
|
||||
|
||||
typedef struct group_el_init_t {
|
||||
enum w_type type;
|
||||
bool is_default;
|
||||
bool is_hidden_value;
|
||||
char label[DIALOGUE_TITLE];
|
||||
int32_t length;
|
||||
enum w_g_dir direction;
|
||||
union {
|
||||
int32_t num;
|
||||
} val;
|
||||
struct group_el_init_t *children;
|
||||
} w_group_el_init_t;
|
||||
|
||||
typedef struct group_t {
|
||||
w_t w;
|
||||
w_g_el_t *elements;
|
||||
enum w_g_dir direction;
|
||||
int32_t count;
|
||||
int32_t first_id, last_id;
|
||||
struct group_t *parent_group;
|
||||
} w_group_t;
|
||||
#include <common.h>
|
||||
|
||||
w_group_t *w_group_init(WINDOW **win, w_t *w_parent, w_group_el_init_t *children,
|
||||
d_array_ptr_t *id_map, enum w_g_dir dir,
|
||||
|
||||
+2
-1
@@ -6,7 +6,8 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../app.h"
|
||||
#include <core/app.h>
|
||||
#include <utils.h>
|
||||
#include "input.h"
|
||||
#include <wchar.h>
|
||||
#include <widget_core.h>
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#ifndef W_INPUT_H
|
||||
#define W_INPUT_H
|
||||
|
||||
#include "../main.h"
|
||||
#include <stdint.h>
|
||||
#include <widget_core.h>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include "progress_bar.h"
|
||||
#include "../app.h"
|
||||
#include <core/app.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -5,6 +5,7 @@ file(GLOB M_SOURCES "*.c")
|
||||
target_link_libraries(widget_core
|
||||
PRIVATE
|
||||
utils
|
||||
common
|
||||
)
|
||||
|
||||
target_sources(widget_core
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
so it is separated from the widget directory */
|
||||
|
||||
#include "widget_core.h"
|
||||
#include "../main.h"
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -4,104 +4,9 @@
|
||||
#ifndef WIDGET_CORE_H
|
||||
#define WIDGET_CORE_H
|
||||
|
||||
#include "../main.h"
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stdint.h>
|
||||
|
||||
enum w_g_type { g_content, g_action };
|
||||
|
||||
enum w_g_dir {
|
||||
horizontal,
|
||||
vertical,
|
||||
};
|
||||
|
||||
enum w_type {
|
||||
w_end,
|
||||
w_button,
|
||||
w_box,
|
||||
w_group,
|
||||
w_input,
|
||||
w_checkbox,
|
||||
w_progress,
|
||||
};
|
||||
|
||||
enum w_val_type {
|
||||
val_num,
|
||||
val_ptr,
|
||||
val_nul,
|
||||
};
|
||||
|
||||
struct w_val_t {
|
||||
enum w_val_type type;
|
||||
union {
|
||||
void *ptr;
|
||||
int32_t num;
|
||||
} val;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int32_t id;
|
||||
int32_t idx; /* element index in the group */
|
||||
void *element;
|
||||
enum w_type type;
|
||||
enum w_g_type g_type;
|
||||
bool is_default;
|
||||
struct w_val_t val;
|
||||
} w_g_el_t;
|
||||
|
||||
/* widget callback response code */
|
||||
enum w_cbrp_code {
|
||||
cbrc_none, /* default */
|
||||
cbrp_val,
|
||||
cbrp_g_el, /* element of type group_el_t */
|
||||
cbrp_err
|
||||
};
|
||||
|
||||
/* widget callback response data */
|
||||
typedef struct {
|
||||
enum w_cbrp_code code;
|
||||
struct w_val_t val;
|
||||
} w_cbrp_data;
|
||||
|
||||
typedef struct {
|
||||
void *app;
|
||||
void *element;
|
||||
void *data;
|
||||
w_cbrp_data resp_data;
|
||||
void *active_el;
|
||||
} w_cb_args_t;
|
||||
|
||||
typedef void (*w_cb_t)(w_cb_args_t *args);
|
||||
typedef int32_t (*w_cb_press_t)(void *app, void *data);
|
||||
|
||||
typedef struct w_t {
|
||||
int32_t id;
|
||||
char title[DIALOGUE_TITLE];
|
||||
struct {
|
||||
int32_t y;
|
||||
int32_t x;
|
||||
} cur; /* cursor position */
|
||||
struct {
|
||||
int32_t y;
|
||||
int32_t x;
|
||||
} sz; /* size */
|
||||
struct {
|
||||
int32_t y;
|
||||
int32_t x;
|
||||
} m; /* margin (relative to the parent) */
|
||||
struct {
|
||||
int32_t y;
|
||||
int32_t x;
|
||||
} ps; /* position (relative to the window) */
|
||||
WINDOW *const *parent_win;
|
||||
struct w_t *w_parent;
|
||||
w_cb_t callback;
|
||||
} w_t;
|
||||
|
||||
enum w_pmt_attrs {
|
||||
PMT_POS_CENTER = 01,
|
||||
PMT_ALIGN_CENTER = 03,
|
||||
};
|
||||
#include <common.h>
|
||||
|
||||
void w_init(w_t *w, w_t *w_parent, WINDOW **win, char *title);
|
||||
int32_t w_get_max_line_len(const char *text, int32_t *line_count);
|
||||
|
||||
@@ -2,13 +2,13 @@ add_library(windows STATIC)
|
||||
|
||||
file(GLOB M_SOURCES "*.c")
|
||||
|
||||
target_link_directories(windows
|
||||
PRIVATE
|
||||
"../widget/"
|
||||
"../widget_core/"
|
||||
"../lib/"
|
||||
"../utils/"
|
||||
)
|
||||
# target_link_directories(windows
|
||||
# PRIVATE
|
||||
# "../widget/"
|
||||
# "../widget_core/"
|
||||
# "../lib/"
|
||||
# "../utils/"
|
||||
# )
|
||||
|
||||
target_link_libraries(windows
|
||||
PRIVATE
|
||||
@@ -16,6 +16,8 @@ target_link_libraries(windows
|
||||
widget_core
|
||||
bstring
|
||||
utils
|
||||
core
|
||||
common
|
||||
)
|
||||
|
||||
target_sources(windows
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include "file_list.h"
|
||||
#include "../app.h"
|
||||
#include "../file_processor.h"
|
||||
#include "../fs.h"
|
||||
#include "../server.h"
|
||||
#include <common.h>
|
||||
#include <core.h>
|
||||
#include "main_window.h"
|
||||
#include <bstrlib.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#ifndef W_FILE_LIST_H
|
||||
#define W_FILE_LIST_H
|
||||
|
||||
#include "../app.h"
|
||||
#include <bstrlib.h>
|
||||
#include <stdint.h>
|
||||
#include <widget_core.h>
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
#include <unistd.h>
|
||||
#include <utils.h>
|
||||
|
||||
#include "../file_processor.h"
|
||||
#include "../fs.h"
|
||||
#include "../server.h"
|
||||
#include <core/file_processor.h>
|
||||
#include <core/fs.h>
|
||||
#include <core/server.h>
|
||||
#include <core/app.h>
|
||||
|
||||
#include "alert.h"
|
||||
#include "fs_file_list.h"
|
||||
#include "main_window.h"
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#ifndef W_LFL_H /* LOCAL FILE LIST */
|
||||
#define W_LFL_H
|
||||
|
||||
#include "../app.h"
|
||||
#include <stddef.h>
|
||||
#include <widget_core.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
#include "fs_file_list.h"
|
||||
#include "text_edit.h"
|
||||
|
||||
#include <core/app.h>
|
||||
#include <widget/dialogue.h>
|
||||
|
||||
void main_window_set(app_t *app, enum main_window_type type) {
|
||||
if (app->main_ui.ui != NULL) {
|
||||
main_window_destroy(app);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#ifndef MAIN_WINDOW_H
|
||||
#define MAIN_WINDOW_H
|
||||
|
||||
#include "../app.h"
|
||||
#include <common.h>
|
||||
|
||||
void main_window_set(app_t *app, enum main_window_type type);
|
||||
void main_window_draw(app_t *app);
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/* Copyright (c) 2026 Oleksandr Zhylin */
|
||||
|
||||
#include "../file_processor.h"
|
||||
#include "../server.h"
|
||||
#include "../windows/main_window.h"
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <ncursesw/ncurses.h>
|
||||
@@ -17,10 +14,15 @@
|
||||
#include <utils.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include <core/file_processor.h>
|
||||
#include <core/server.h>
|
||||
#include <core/app.h>
|
||||
|
||||
#include "d_array.h"
|
||||
#include "text_edit.h"
|
||||
#include "file_list.h"
|
||||
#include "text_edit.h"
|
||||
#include "widget_core.h"
|
||||
#include "main_window.h"
|
||||
|
||||
/* TODO: расширять capacity линии при достижении лимита */
|
||||
|
||||
@@ -210,6 +212,9 @@ static int32_t process_user_input(app_t *app, w_cb_args_t *d_args) {
|
||||
/* Save */
|
||||
break;
|
||||
case KEY_F(8):
|
||||
server_send_string(app->query_args, "[Empty description]\n:END:\n");
|
||||
app->query_args->state = S_WAIT_SERVER;
|
||||
app->callback_after_notification = callback_after_notification;
|
||||
/* Cancel */
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#ifndef W_TEXT_EDIT_H /* LOCAL FILE LIST */
|
||||
#define W_TEXT_EDIT_H
|
||||
|
||||
#include "../app.h"
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
project("My Awesome Tested Project" C)
|
||||
cmake_minimum_required(VERSION 3.0) # Let's tell it a bit about our environment
|
||||
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) # We declare a variable to select what to build
|
||||
set (TARGET_GROUP production CACHE STRING "What Should I Build?") # Modules that exist in both our release and our unit tests, are added now
|
||||
add_subdirectory(src) if(TARGET_GROUP STREQUAL release) # If we had any files that ONLY were compiled into our release, for example startup code,
|
||||
# it's really handy to put it here.
|
||||
elseif(TARGET_GROUP STREQUAL test) # Our tests need to let CTest know we're running tests
|
||||
include(CTest) # It also needs to understand we have code in source and test directories
|
||||
add_subdirectory(unity)
|
||||
add_subdirectory(test)
|
||||
else()
|
||||
message(FATAL_ERROR "I don't know the TARGET_GROUP you gave me!")
|
||||
endif()
|
||||
Reference in New Issue
Block a user