mirror of
https://github.com/albfan/miraclecast.git
synced 2025-03-09 23:38:56 +00:00
add struct wfd_arg to generalize passing of arguments
This commit is contained in:
parent
21986b4cea
commit
495675cdd4
4 changed files with 439 additions and 1 deletions
|
@ -53,7 +53,8 @@ set(miracle-wfdctl_SRCS ctl-cli.c
|
||||||
wfd-session.c
|
wfd-session.c
|
||||||
wfd-out-session.c
|
wfd-out-session.c
|
||||||
wfdctl.c
|
wfdctl.c
|
||||||
wfd.c)
|
wfd.c
|
||||||
|
wfd-arg.c)
|
||||||
|
|
||||||
include_directories(${CMAKE_BINARY_DIR}
|
include_directories(${CMAKE_BINARY_DIR}
|
||||||
${CMAKE_SOURCE_DIR}/src
|
${CMAKE_SOURCE_DIR}/src
|
||||||
|
|
61
src/ctl/wfd-arg.c
Normal file
61
src/ctl/wfd-arg.c
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* MiracleCast - Wifi-Display/Miracast Implementation
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
|
||||||
|
*
|
||||||
|
* MiracleCast is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* MiracleCast 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
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with MiracleCast; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include <errno.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "wfd-arg.h"
|
||||||
|
|
||||||
|
int wfd_arg_list_new(struct wfd_arg_list **out)
|
||||||
|
{
|
||||||
|
assert(out);
|
||||||
|
|
||||||
|
struct wfd_arg_list *l = calloc(1, sizeof(struct wfd_arg_list));
|
||||||
|
if(!l) {
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
l->dynamic = true;
|
||||||
|
|
||||||
|
*out = l;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wfd_arg_list_clear(struct wfd_arg_list *l)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
struct wfd_arg *arg;
|
||||||
|
|
||||||
|
if(!l || !l->dynamic) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
arg = l->discrete ? l->argv : l->args;
|
||||||
|
for(i = 0; i < l->len; i ++) {
|
||||||
|
if((WFD_ARG_STR == arg->type || WFD_ARG_PTR == arg->type)
|
||||||
|
&& arg->ptr && arg->free) {
|
||||||
|
(*arg->free)(arg->ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(l->discrete) {
|
||||||
|
free(l->argv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
210
src/ctl/wfd-arg.h
Normal file
210
src/ctl/wfd-arg.h
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
/*
|
||||||
|
* MiracleCast - Wifi-Display/Miracast Implementation
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
|
||||||
|
*
|
||||||
|
* MiracleCast is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* MiracleCast 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
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with MiracleCast; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "shl_macro.h"
|
||||||
|
|
||||||
|
#ifndef CTL_ARG_H
|
||||||
|
#define CTL_ARG_H
|
||||||
|
|
||||||
|
#define wfd_arg_i8(v) { .type = WFD_ARG_I8, .i8 = (v) }
|
||||||
|
#define wfd_arg_u8(v) { .type = WFD_ARG_U8, .u8 = (v) }
|
||||||
|
#define wfd_arg_i16(v) { .type = WFD_ARG_I16, .i16 = (v) }
|
||||||
|
#define wfd_arg_u16(v) { .type = WFD_ARG_U16, .u16 = (v) }
|
||||||
|
#define wfd_arg_i32(v) { .type = WFD_ARG_I32, .i32 = (v) }
|
||||||
|
#define wfd_arg_u32(v) { .type = WFD_ARG_U32, .u32 = (v) }
|
||||||
|
#define wfd_arg_i64(v) { .type = WFD_ARG_I64, .i64 = (v) }
|
||||||
|
#define wfd_arg_u64(v) { .type = WFD_ARG_U64, .u64 = (v) }
|
||||||
|
#define wfd_arg_cstr(v) { .type = WFD_ARG_CSTR, .ptr = (v) }
|
||||||
|
#define wfd_arg_cptr(v) { .type = WFD_ARG_CPTR, .ptr = (v) }
|
||||||
|
#define wfd_arg_dict(_k, _v) { \
|
||||||
|
.type = WFD_ARG_DICT, \
|
||||||
|
.k = (struct wfd_arg[]){_k}, \
|
||||||
|
.v = (struct wfd_arg[]){_v} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define wfd_arg_type_id(_t) _Generic((_t), \
|
||||||
|
int8_t: WFD_ARG_I8, \
|
||||||
|
uint8_t: WFD_ARG_U8, \
|
||||||
|
int16_t: WFD_ARG_I16, \
|
||||||
|
uint16_t: WFD_ARG_U16, \
|
||||||
|
int32_t: WFD_ARG_I32, \
|
||||||
|
uint32_t: WFD_ARG_U32, \
|
||||||
|
int64_t: WFD_ARG_I64, \
|
||||||
|
uint64_t: WFD_ARG_U64, \
|
||||||
|
const char *: WFD_ARG_CSTR, \
|
||||||
|
char *: WFD_ARG_STR, \
|
||||||
|
const void *: WFD_ARG_CPTR, \
|
||||||
|
default: WFD_ARG_PTR)
|
||||||
|
|
||||||
|
#define wfd_arg_list(...) { \
|
||||||
|
.argv = (struct wfd_arg[]) { \
|
||||||
|
__VA_ARGS__ \
|
||||||
|
}, \
|
||||||
|
.discrete = true, \
|
||||||
|
.dynamic = false, \
|
||||||
|
.len = (sizeof((struct wfd_arg[]){ __VA_ARGS__ })/sizeof(struct wfd_arg)) \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define wfd_arg_get(_a, _v) ({ \
|
||||||
|
*(_v) = _Generic((*_v), \
|
||||||
|
int8_t: wfd_arg_get_i8, \
|
||||||
|
uint8_t: wfd_arg_get_u8, \
|
||||||
|
int16_t: wfd_arg_get_i16, \
|
||||||
|
uint16_t: wfd_arg_get_u16, \
|
||||||
|
int32_t: wfd_arg_get_i32, \
|
||||||
|
uint32_t: wfd_arg_get_u32, \
|
||||||
|
int64_t: wfd_arg_get_i64, \
|
||||||
|
uint64_t: wfd_arg_get_u64, \
|
||||||
|
char *: wfd_arg_get_str, \
|
||||||
|
const char *: wfd_arg_get_cstr, \
|
||||||
|
const void *: wfd_arg_get_cptr, \
|
||||||
|
default: wfd_arg_get_ptr \
|
||||||
|
)(_a); \
|
||||||
|
})
|
||||||
|
|
||||||
|
#define wfd_arg_getkv(_a, _k, _v) ({ \
|
||||||
|
assert(_a); \
|
||||||
|
assert(WFD_ARG_DICT == (_a)->type); \
|
||||||
|
wfd_arg_get((_a)->k, (_k)); \
|
||||||
|
wfd_arg_get((_a)->v, (_v)); \
|
||||||
|
})
|
||||||
|
|
||||||
|
#define wfd_arg_list_get(_l, _i, _v) ({ \
|
||||||
|
assert(_l); \
|
||||||
|
assert((_i) >= 0 && (_i) < (_l)->len); \
|
||||||
|
struct wfd_arg *arg = (_l)->discrete \
|
||||||
|
? &(_l)->argv[(_i)] \
|
||||||
|
: &(_l)->args[(_i)]; \
|
||||||
|
wfd_arg_get(arg, _v); \
|
||||||
|
})
|
||||||
|
|
||||||
|
#define wfd_arg_list_getkv(_l, _i, _k, _v) ({ \
|
||||||
|
assert(_l); \
|
||||||
|
assert((_i) >= 0 && (_i) < (_l)->len); \
|
||||||
|
struct wfd_arg *arg = (_l)->discrete \
|
||||||
|
? &(_l)->argv[(_i)] \
|
||||||
|
: &(_l)->args[(_i)]; \
|
||||||
|
wfd_arg_getkv(arg, (_k), (_v)); \
|
||||||
|
})
|
||||||
|
|
||||||
|
enum wfd_arg_type
|
||||||
|
{
|
||||||
|
WFD_ARG_NONE,
|
||||||
|
WFD_ARG_I8,
|
||||||
|
WFD_ARG_I16,
|
||||||
|
WFD_ARG_I32,
|
||||||
|
WFD_ARG_I64,
|
||||||
|
WFD_ARG_U8,
|
||||||
|
WFD_ARG_U16,
|
||||||
|
WFD_ARG_U32,
|
||||||
|
WFD_ARG_U64,
|
||||||
|
WFD_ARG_STR,
|
||||||
|
WFD_ARG_CSTR,
|
||||||
|
WFD_ARG_PTR,
|
||||||
|
WFD_ARG_CPTR,
|
||||||
|
WFD_ARG_DICT,
|
||||||
|
WFD_ARG_CB,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wfd_arg
|
||||||
|
{
|
||||||
|
enum wfd_arg_type type;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
int8_t i8;
|
||||||
|
uint8_t u8;
|
||||||
|
int16_t i16;
|
||||||
|
uint16_t u16;
|
||||||
|
int32_t i32;
|
||||||
|
uint32_t u32;
|
||||||
|
int64_t i64;
|
||||||
|
uint64_t u64;
|
||||||
|
struct {
|
||||||
|
void *ptr;
|
||||||
|
void (*free)(void *);
|
||||||
|
};
|
||||||
|
struct {
|
||||||
|
struct wfd_arg *k;
|
||||||
|
struct wfd_arg *v;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wfd_arg_list
|
||||||
|
{
|
||||||
|
size_t len: sizeof(size_t) - 2;
|
||||||
|
bool discrete: 1;
|
||||||
|
bool dynamic: 1;
|
||||||
|
|
||||||
|
union {
|
||||||
|
struct wfd_arg * argv;
|
||||||
|
struct wfd_arg args[0];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
int wfd_arg_list_new(struct wfd_arg_list **out);
|
||||||
|
void wfd_arg_list_clear(struct wfd_arg_list *l);
|
||||||
|
static inline void wfd_arg_list_free(struct wfd_arg_list *l);
|
||||||
|
|
||||||
|
static inline enum wfd_arg_type wfd_arg_get_type(struct wfd_arg *a);
|
||||||
|
static inline void wfd_arg_free_ptr(struct wfd_arg *a);
|
||||||
|
static inline void wfd_arg_clear(struct wfd_arg *a);
|
||||||
|
|
||||||
|
static inline int8_t wfd_arg_get_i8(const struct wfd_arg *a);
|
||||||
|
static inline void wfd_arg_set_i8(struct wfd_arg *a, int8_t v);
|
||||||
|
|
||||||
|
static inline uint8_t wfd_arg_get_u8(const struct wfd_arg *a);
|
||||||
|
static inline void wfd_arg_set_u8(struct wfd_arg *a, uint8_t v);
|
||||||
|
|
||||||
|
static inline int16_t wfd_arg_get_i16(const struct wfd_arg *a);
|
||||||
|
static inline void wfd_arg_set_i16(struct wfd_arg *a, int16_t v);
|
||||||
|
|
||||||
|
static inline uint16_t wfd_arg_get_u16(const struct wfd_arg *a);
|
||||||
|
static inline void wfd_arg_set_u16(struct wfd_arg *a, uint16_t v);
|
||||||
|
|
||||||
|
static inline int32_t wfd_arg_get_i32(const struct wfd_arg *a);
|
||||||
|
static inline void wfd_arg_set_i32(struct wfd_arg *a, int32_t v);
|
||||||
|
|
||||||
|
static inline uint32_t wfd_arg_get_u32(const struct wfd_arg *a);
|
||||||
|
static inline void wfd_arg_set_u32(struct wfd_arg *a, uint32_t v);
|
||||||
|
|
||||||
|
static inline int64_t wfd_arg_get_i64(const struct wfd_arg *a);
|
||||||
|
static inline void wfd_arg_set_i64(struct wfd_arg *a, int64_t v);
|
||||||
|
|
||||||
|
static inline uint64_t wfd_arg_get_u64(const struct wfd_arg *a);
|
||||||
|
static inline void wfd_arg_set_u64(struct wfd_arg *a, uint64_t v);
|
||||||
|
|
||||||
|
static inline const char * wfd_arg_get_cstr(const struct wfd_arg *a);
|
||||||
|
static inline void wfd_arg_set_cstr(struct wfd_arg *a, const char * v);
|
||||||
|
|
||||||
|
static inline void wfd_arg_take_str(struct wfd_arg *a, char *v);
|
||||||
|
static inline char * wfd_arg_get_str(const struct wfd_arg *a);
|
||||||
|
static inline int wfd_arg_set_str(struct wfd_arg *a, const char *v);;
|
||||||
|
|
||||||
|
static inline const void * wfd_arg_get_cptr(const struct wfd_arg *a);
|
||||||
|
static inline void wfd_arg_set_cptr(struct wfd_arg *a, const void * v);
|
||||||
|
|
||||||
|
static inline void wfd_arg_take_ptr(struct wfd_arg *a, void *v, void (*f)(void *));
|
||||||
|
static inline void * wfd_arg_get_ptr(const struct wfd_arg *a);
|
||||||
|
|
||||||
|
#include "wfd-arg.inc"
|
||||||
|
|
||||||
|
#endif /* CTL_ARG_H */
|
166
src/ctl/wfd-arg.inc
Normal file
166
src/ctl/wfd-arg.inc
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
/*
|
||||||
|
* MiracleCast - Wifi-Display/Miracast Implementation
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
|
||||||
|
*
|
||||||
|
* MiracleCast is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* MiracleCast 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
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with MiracleCast; If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#define wfd_arg_getter(_t, _s, _S) \
|
||||||
|
static inline _t wfd_arg_get_##_s(const struct wfd_arg *a) \
|
||||||
|
{ \
|
||||||
|
assert(a); \
|
||||||
|
assert(WFD_ARG_##_S == a->type); \
|
||||||
|
return a->_s; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define wfd_arg_setter(_t, _s, _S) \
|
||||||
|
static inline void wfd_arg_set_##_t(struct wfd_arg *a, _t v) \
|
||||||
|
{ \
|
||||||
|
assert(a); \
|
||||||
|
assert(!a->type || WFD_ARG_##_S == a->type); \
|
||||||
|
*a = (struct wfd_arg) { .type = WFD_ARG_##_S, ._s = v }; \
|
||||||
|
}
|
||||||
|
|
||||||
|
wfd_arg_getter(int8_t, i8, I8)
|
||||||
|
wfd_arg_setter(int8_t, i8, I8)
|
||||||
|
wfd_arg_getter(uint8_t, u8, U8)
|
||||||
|
wfd_arg_setter(uint8_t, u8, U8)
|
||||||
|
wfd_arg_getter(int16_t, i16, I16)
|
||||||
|
wfd_arg_setter(int16_t, i16, I16)
|
||||||
|
wfd_arg_getter(uint16_t, u16, U16)
|
||||||
|
wfd_arg_setter(uint16_t, u16, U16)
|
||||||
|
wfd_arg_getter(int32_t, i32, I32)
|
||||||
|
wfd_arg_setter(int32_t, i32, I32)
|
||||||
|
wfd_arg_getter(uint32_t, u32, U32)
|
||||||
|
wfd_arg_setter(uint32_t, u32, U32)
|
||||||
|
wfd_arg_getter(int64_t, i64, I64)
|
||||||
|
wfd_arg_setter(int64_t, i64, I64)
|
||||||
|
wfd_arg_getter(uint64_t, u64, U64)
|
||||||
|
wfd_arg_setter(uint64_t, u64, U64)
|
||||||
|
|
||||||
|
static inline void wfd_arg_list_free(struct wfd_arg_list *l)
|
||||||
|
{
|
||||||
|
wfd_arg_list_clear(l);
|
||||||
|
free(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline enum wfd_arg_type wfd_arg_get_type(struct wfd_arg *a)
|
||||||
|
{
|
||||||
|
assert(a);
|
||||||
|
|
||||||
|
return a->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void wfd_arg_free_ptr(struct wfd_arg *a)
|
||||||
|
{
|
||||||
|
if(!a || (WFD_ARG_STR != a->type && WFD_ARG_PTR != a->type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(a->ptr && a->free) {
|
||||||
|
(*a->free)(a->ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void wfd_arg_clear(struct wfd_arg *a)
|
||||||
|
{
|
||||||
|
if(a) {
|
||||||
|
wfd_arg_free_ptr(a);
|
||||||
|
memset(a, 0, sizeof(*a));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline const char * wfd_arg_get_cstr(const struct wfd_arg *a)
|
||||||
|
{
|
||||||
|
assert(a);
|
||||||
|
assert(WFD_ARG_CSTR == a->type || WFD_ARG_STR == a->type);
|
||||||
|
|
||||||
|
return a->ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void wfd_arg_set_cstr(struct wfd_arg *a, const char * v)
|
||||||
|
{
|
||||||
|
assert(a);
|
||||||
|
assert(!a->type || WFD_ARG_CSTR == a->type);
|
||||||
|
|
||||||
|
*a = (struct wfd_arg) { .type = WFD_ARG_CSTR, .ptr = (void *) v };
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline char * wfd_arg_get_str(const struct wfd_arg *a)
|
||||||
|
{
|
||||||
|
assert(a);
|
||||||
|
assert(WFD_ARG_STR == a->type);
|
||||||
|
|
||||||
|
return a->ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void wfd_arg_take_str(struct wfd_arg *a, char *v)
|
||||||
|
{
|
||||||
|
assert(a);
|
||||||
|
assert(!a->type || WFD_ARG_STR == a->type || WFD_ARG_CSTR == a->type);
|
||||||
|
|
||||||
|
wfd_arg_free_ptr(a);
|
||||||
|
*a = (struct wfd_arg) { .type = WFD_ARG_STR, .ptr = v, .free = free };
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int wfd_arg_set_str(struct wfd_arg *a, const char *v)
|
||||||
|
{
|
||||||
|
char *s;
|
||||||
|
|
||||||
|
assert(a);
|
||||||
|
assert(!a->type || WFD_ARG_STR == a->type);
|
||||||
|
|
||||||
|
s = strdup(v);
|
||||||
|
if(!s) {
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
wfd_arg_take_str(a, s);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline const void * wfd_arg_get_cptr(const struct wfd_arg *a)
|
||||||
|
{
|
||||||
|
assert(a);
|
||||||
|
assert(WFD_ARG_PTR <= a->type && WFD_ARG_CPTR == a->type);
|
||||||
|
|
||||||
|
return a->ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void wfd_arg_set_cptr(struct wfd_arg *a, const void * v)
|
||||||
|
{
|
||||||
|
assert(a);
|
||||||
|
assert(!a->type || WFD_ARG_CSTR == a->type);
|
||||||
|
|
||||||
|
*a = (struct wfd_arg) { .type = WFD_ARG_CPTR, .ptr = (void *) v };
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void * wfd_arg_get_ptr(const struct wfd_arg *a)
|
||||||
|
{
|
||||||
|
assert(a);
|
||||||
|
assert(WFD_ARG_PTR == a->type || WFD_ARG_STR == a->type);
|
||||||
|
|
||||||
|
return a->ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void wfd_arg_take_ptr(struct wfd_arg *a, void *v, void (*f)(void *))
|
||||||
|
{
|
||||||
|
assert(a);
|
||||||
|
assert(!a->type || WFD_ARG_PTR == a->type);
|
||||||
|
|
||||||
|
wfd_arg_free_ptr(a);
|
||||||
|
*a = (struct wfd_arg) { .type = WFD_ARG_PTR, .ptr = v, .free = f };
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue