Compare commits
3 Commits
17c9d088cc
...
731e377b6e
Author | SHA1 | Date | |
---|---|---|---|
731e377b6e | |||
0d0b692960 | |||
c2323ab43d |
@ -21,7 +21,7 @@
|
|||||||
* @file gds-tree-checker.c
|
* @file gds-tree-checker.c
|
||||||
* @brief Checking functions of a cell tree
|
* @brief Checking functions of a cell tree
|
||||||
*
|
*
|
||||||
* This file contains cehcking functions for the GDS cell tree.
|
* This file contains checking functions for the GDS cell tree.
|
||||||
* These functions include checks if all child references could be resolved,
|
* These functions include checks if all child references could be resolved,
|
||||||
* and if the cell tree contains loops.
|
* and if the cell tree contains loops.
|
||||||
*
|
*
|
||||||
|
@ -106,12 +106,14 @@ const char *gds_output_renderer_get_output_file(GdsOutputRenderer *renderer);
|
|||||||
* @brief Get layer settings
|
* @brief Get layer settings
|
||||||
*
|
*
|
||||||
* This is a convenience function for getting the
|
* This is a convenience function for getting the
|
||||||
* "layer-settings" property
|
* "layer-settings" property. This also references it.
|
||||||
|
* This is to prevent race conditions with another thread that might
|
||||||
|
* alter the layer settings before they are read out.
|
||||||
*
|
*
|
||||||
* @param renderer Renderer
|
* @param renderer Renderer
|
||||||
* @return Layer settings object
|
* @return Layer settings object
|
||||||
*/
|
*/
|
||||||
LayerSettings *gds_output_renderer_get_layer_settings(GdsOutputRenderer *renderer);
|
LayerSettings *gds_output_renderer_get_and_ref_layer_settings(GdsOutputRenderer *renderer);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set layer settings
|
* @brief Set layer settings
|
||||||
@ -127,6 +129,21 @@ LayerSettings *gds_output_renderer_get_layer_settings(GdsOutputRenderer *rendere
|
|||||||
*/
|
*/
|
||||||
void gds_output_renderer_set_layer_settings(GdsOutputRenderer *renderer, LayerSettings *settings);
|
void gds_output_renderer_set_layer_settings(GdsOutputRenderer *renderer, LayerSettings *settings);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Render output asynchronously
|
||||||
|
*
|
||||||
|
* This function will render in a separate thread.
|
||||||
|
* To wait for the completion of the rendering process.
|
||||||
|
*
|
||||||
|
* @note A second async thread cannot be spawned.
|
||||||
|
*
|
||||||
|
* @param renderer Output renderer
|
||||||
|
* @param cell Cell to render
|
||||||
|
* @param scale Scale
|
||||||
|
* @return 0 if successful. In case no thread can be spawned < 0
|
||||||
|
*/
|
||||||
|
int gds_output_renderer_render_output_async(GdsOutputRenderer *renderer, struct gds_cell *cell, double scale);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
#endif /* _GDS_OUTPUT_RENDERER_H_ */
|
#endif /* _GDS_OUTPUT_RENDERER_H_ */
|
||||||
|
@ -35,6 +35,7 @@ struct _ColorPalette {
|
|||||||
/** @brief The length of the _ColorPalette::color_array array */
|
/** @brief The length of the _ColorPalette::color_array array */
|
||||||
unsigned int color_array_length;
|
unsigned int color_array_length;
|
||||||
|
|
||||||
|
/* Dummy bytes to ensure ABI compatibility in future versions */
|
||||||
gpointer dummy[4];
|
gpointer dummy[4];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -116,8 +117,8 @@ static int color_palette_fill_with_resource(ColorPalette *palette, char *resourc
|
|||||||
palette->color_array = (GdkRGBA *)malloc(sizeof(GdkRGBA) * (unsigned int)lines);
|
palette->color_array = (GdkRGBA *)malloc(sizeof(GdkRGBA) * (unsigned int)lines);
|
||||||
|
|
||||||
/* Setup regex for hexadecimal RGB colors like 'A0CB3F' */
|
/* Setup regex for hexadecimal RGB colors like 'A0CB3F' */
|
||||||
regex = g_regex_new("^(?<red>[0-9A-Fa-f][0-9A-Fa-f])(?<green>[0-9A-Fa-f][0-9A-Fa-f])(?<blue>[0-9A-Fa-f][0-9A-Fa-f])$", 0, 0, NULL);
|
regex = g_regex_new("^(?<red>[0-9A-Fa-f][0-9A-Fa-f])(?<green>[0-9A-Fa-f][0-9A-Fa-f])(?<blue>[0-9A-Fa-f][0-9A-Fa-f])$",
|
||||||
|
0, 0, NULL);
|
||||||
|
|
||||||
/* Reset line */
|
/* Reset line */
|
||||||
line_idx = 0;
|
line_idx = 0;
|
||||||
@ -227,11 +228,27 @@ unsigned int color_palette_get_color_count(ColorPalette *palette)
|
|||||||
return return_val;
|
return return_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void color_palette_dispose(GObject *gobj)
|
||||||
|
{
|
||||||
|
ColorPalette *palette;
|
||||||
|
|
||||||
|
palette = GDS_RENDER_COLOR_PALETTE(gobj);
|
||||||
|
if (palette->color_array)
|
||||||
|
{
|
||||||
|
palette->color_array_length = 0;
|
||||||
|
free(palette->color_array);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chain up to parent class */
|
||||||
|
G_OBJECT_CLASS(color_palette_parent_class)->dispose(gobj);
|
||||||
|
}
|
||||||
|
|
||||||
static void color_palette_class_init(ColorPaletteClass *klass)
|
static void color_palette_class_init(ColorPaletteClass *klass)
|
||||||
{
|
{
|
||||||
(void)klass;
|
GObjectClass *gclass;
|
||||||
/* Nothing to do for now */
|
|
||||||
return;
|
gclass = G_OBJECT_CLASS(klass);
|
||||||
|
gclass->dispose = color_palette_dispose;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void color_palette_init(ColorPalette *self)
|
static void color_palette_init(ColorPalette *self)
|
||||||
|
@ -74,7 +74,7 @@ static void revert_inherited_transform(struct cairo_layer *layers)
|
|||||||
* @param origin Origin translation
|
* @param origin Origin translation
|
||||||
* @param magnification Scaling
|
* @param magnification Scaling
|
||||||
* @param flipping Mirror image on x-axis before rotating
|
* @param flipping Mirror image on x-axis before rotating
|
||||||
* @param rotation Rotattion in degrees
|
* @param rotation Rotation in degrees
|
||||||
* @param scale Scale the image down by. Only used for sclaing origin coordinates. Not applied to layer.
|
* @param scale Scale the image down by. Only used for sclaing origin coordinates. Not applied to layer.
|
||||||
*/
|
*/
|
||||||
static void apply_inherited_transform_to_all_layers(struct cairo_layer *layers,
|
static void apply_inherited_transform_to_all_layers(struct cairo_layer *layers,
|
||||||
@ -140,7 +140,9 @@ static void render_cell(struct gds_cell *cell, struct cairo_layer *layers, doubl
|
|||||||
/* Get layer renderer */
|
/* Get layer renderer */
|
||||||
if (gfx->layer >= MAX_LAYERS)
|
if (gfx->layer >= MAX_LAYERS)
|
||||||
continue;
|
continue;
|
||||||
if ((cr = layers[gfx->layer].cr) == NULL)
|
|
||||||
|
cr = layers[gfx->layer].cr;
|
||||||
|
if (cr == NULL)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Apply settings */
|
/* Apply settings */
|
||||||
@ -167,7 +169,6 @@ static void render_cell(struct gds_cell *cell, struct cairo_layer *layers, doubl
|
|||||||
cairo_move_to(cr, vertex->x/scale, vertex->y/scale);
|
cairo_move_to(cr, vertex->x/scale, vertex->y/scale);
|
||||||
else
|
else
|
||||||
cairo_line_to(cr, vertex->x/scale, vertex->y/scale);
|
cairo_line_to(cr, vertex->x/scale, vertex->y/scale);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create graphics object */
|
/* Create graphics object */
|
||||||
@ -183,9 +184,7 @@ static void render_cell(struct gds_cell *cell, struct cairo_layer *layers, doubl
|
|||||||
cairo_fill(cr);
|
cairo_fill(cr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
} /* for gfx list */
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -377,12 +376,13 @@ static int cairo_renderer_render_output(GdsOutputRenderer *renderer,
|
|||||||
LayerSettings *settings;
|
LayerSettings *settings;
|
||||||
GList *layer_infos = NULL;
|
GList *layer_infos = NULL;
|
||||||
const char *output_file;
|
const char *output_file;
|
||||||
|
int ret;
|
||||||
|
|
||||||
if (!c_renderer)
|
if (!c_renderer)
|
||||||
return -2000;
|
return -2000;
|
||||||
|
|
||||||
output_file = gds_output_renderer_get_output_file(renderer);
|
output_file = gds_output_renderer_get_output_file(renderer);
|
||||||
settings = gds_output_renderer_get_layer_settings(renderer);
|
settings = gds_output_renderer_get_and_ref_layer_settings(renderer);
|
||||||
|
|
||||||
/* Set layer info list. In case of failure it remains NULL */
|
/* Set layer info list. In case of failure it remains NULL */
|
||||||
if (settings)
|
if (settings)
|
||||||
@ -393,7 +393,12 @@ static int cairo_renderer_render_output(GdsOutputRenderer *renderer,
|
|||||||
else
|
else
|
||||||
pdf_file = output_file;
|
pdf_file = output_file;
|
||||||
|
|
||||||
return cairo_renderer_render_cell_to_vector_file(cell, layer_infos, pdf_file, svg_file, scale);
|
ret = cairo_renderer_render_cell_to_vector_file(cell, layer_infos, pdf_file, svg_file, scale);
|
||||||
|
|
||||||
|
if (settings)
|
||||||
|
g_object_unref(settings);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cairo_renderer_class_init(CairoRendererClass *klass)
|
static void cairo_renderer_class_init(CairoRendererClass *klass)
|
||||||
|
@ -105,15 +105,20 @@ static int external_renderer_render_output(GdsOutputRenderer *renderer,
|
|||||||
LayerSettings *settings;
|
LayerSettings *settings;
|
||||||
GList *layer_infos = NULL;
|
GList *layer_infos = NULL;
|
||||||
const char *output_file;
|
const char *output_file;
|
||||||
|
int ret;
|
||||||
|
|
||||||
output_file = gds_output_renderer_get_output_file(renderer);
|
output_file = gds_output_renderer_get_output_file(renderer);
|
||||||
settings = gds_output_renderer_get_layer_settings(renderer);
|
settings = gds_output_renderer_get_and_ref_layer_settings(renderer);
|
||||||
|
|
||||||
/* Set layer info list. In case of failure it remains NULL */
|
/* Set layer info list. In case of failure it remains NULL */
|
||||||
if (settings)
|
if (settings)
|
||||||
layer_infos = layer_settings_get_layer_info_list(settings);
|
layer_infos = layer_settings_get_layer_info_list(settings);
|
||||||
|
|
||||||
return external_renderer_render_cell(cell, layer_infos, output_file, scale, ext_renderer->shared_object_path);
|
ret = external_renderer_render_cell(cell, layer_infos, output_file, scale, ext_renderer->shared_object_path);
|
||||||
|
if (settings)
|
||||||
|
g_object_unref(settings);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void external_renderer_get_property(GObject *obj, guint property_id, GValue *value, GParamSpec *pspec)
|
static void external_renderer_get_property(GObject *obj, guint property_id, GValue *value, GParamSpec *pspec)
|
||||||
|
@ -33,9 +33,18 @@
|
|||||||
#include <gds-render/output-renderers/gds-output-renderer.h>
|
#include <gds-render/output-renderers/gds-output-renderer.h>
|
||||||
#include <gds-render/layer/layer-info.h>
|
#include <gds-render/layer/layer-info.h>
|
||||||
|
|
||||||
|
struct renderer_params {
|
||||||
|
struct gds_cell *cell;
|
||||||
|
double scale;
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
gchar *output_file;
|
gchar *output_file;
|
||||||
LayerSettings *layer_settings;
|
LayerSettings *layer_settings;
|
||||||
|
GMutex settings_lock;
|
||||||
|
gboolean mutex_init_status;
|
||||||
|
GThread *thread;
|
||||||
|
struct renderer_params async_params;
|
||||||
gpointer padding[11];
|
gpointer padding[11];
|
||||||
} GdsOutputRendererPrivate;
|
} GdsOutputRendererPrivate;
|
||||||
|
|
||||||
@ -47,6 +56,9 @@ enum {
|
|||||||
|
|
||||||
G_DEFINE_TYPE_WITH_PRIVATE(GdsOutputRenderer, gds_output_renderer, G_TYPE_OBJECT)
|
G_DEFINE_TYPE_WITH_PRIVATE(GdsOutputRenderer, gds_output_renderer, G_TYPE_OBJECT)
|
||||||
|
|
||||||
|
enum gds_output_renderer_signal_ids {THREAD_JOINED = 0, GDS_OUTPUT_RENDERER_SIGNAL_COUNT};
|
||||||
|
static guint gds_output_renderer_signals[GDS_OUTPUT_RENDERER_SIGNAL_COUNT];
|
||||||
|
|
||||||
static int gds_output_renderer_render_dummy(GdsOutputRenderer *renderer,
|
static int gds_output_renderer_render_dummy(GdsOutputRenderer *renderer,
|
||||||
struct gds_cell *cell,
|
struct gds_cell *cell,
|
||||||
double scale)
|
double scale)
|
||||||
@ -65,6 +77,15 @@ static void gds_output_renderer_dispose(GObject *self_obj)
|
|||||||
GdsOutputRendererPrivate *priv;
|
GdsOutputRendererPrivate *priv;
|
||||||
|
|
||||||
priv = gds_output_renderer_get_instance_private(renderer);
|
priv = gds_output_renderer_get_instance_private(renderer);
|
||||||
|
|
||||||
|
if (priv->mutex_init_status) {
|
||||||
|
/* Try locking the mutex, to test if it's free */
|
||||||
|
g_mutex_lock(&priv->settings_lock);
|
||||||
|
g_mutex_unlock(&priv->settings_lock);
|
||||||
|
g_mutex_clear(&priv->settings_lock);
|
||||||
|
priv->mutex_init_status = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
if (priv->output_file)
|
if (priv->output_file)
|
||||||
g_free(priv->output_file);
|
g_free(priv->output_file);
|
||||||
|
|
||||||
@ -103,14 +124,18 @@ static void gds_output_renderer_set_property(GObject *obj, guint property_id, co
|
|||||||
|
|
||||||
switch (property_id) {
|
switch (property_id) {
|
||||||
case PROP_OUTPUT_FILE:
|
case PROP_OUTPUT_FILE:
|
||||||
|
g_mutex_lock(&priv->settings_lock);
|
||||||
if (priv->output_file)
|
if (priv->output_file)
|
||||||
g_free(priv->output_file);
|
g_free(priv->output_file);
|
||||||
priv->output_file = g_strdup(g_value_get_string(value));
|
priv->output_file = g_strdup(g_value_get_string(value));
|
||||||
|
g_mutex_unlock(&priv->settings_lock);
|
||||||
break;
|
break;
|
||||||
case PROP_LAYER_SETTINGS:
|
case PROP_LAYER_SETTINGS:
|
||||||
|
g_mutex_lock(&priv->settings_lock);
|
||||||
g_clear_object(&priv->layer_settings);
|
g_clear_object(&priv->layer_settings);
|
||||||
priv->layer_settings = g_value_get_object(value);
|
priv->layer_settings = g_value_get_object(value);
|
||||||
g_object_ref(priv->layer_settings);
|
g_object_ref(priv->layer_settings);
|
||||||
|
g_mutex_unlock(&priv->settings_lock);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
|
||||||
@ -139,6 +164,18 @@ static void gds_output_renderer_class_init(GdsOutputRendererClass *klass)
|
|||||||
"Object containing the layer rendering information",
|
"Object containing the layer rendering information",
|
||||||
GDS_RENDER_TYPE_LAYER_SETTINGS, G_PARAM_READWRITE);
|
GDS_RENDER_TYPE_LAYER_SETTINGS, G_PARAM_READWRITE);
|
||||||
g_object_class_install_properties(oclass, N_PROPERTIES, gds_output_renderer_properties);
|
g_object_class_install_properties(oclass, N_PROPERTIES, gds_output_renderer_properties);
|
||||||
|
|
||||||
|
/* Setup output signals */
|
||||||
|
gds_output_renderer_signals[THREAD_JOINED] =
|
||||||
|
g_signal_newv("thread-joined", GDS_RENDER_TYPE_OUTPUT_RENDERER,
|
||||||
|
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
G_TYPE_NONE,
|
||||||
|
0,
|
||||||
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void gds_output_renderer_init(GdsOutputRenderer *self)
|
void gds_output_renderer_init(GdsOutputRenderer *self)
|
||||||
@ -147,7 +184,12 @@ void gds_output_renderer_init(GdsOutputRenderer *self)
|
|||||||
|
|
||||||
priv = gds_output_renderer_get_instance_private(self);
|
priv = gds_output_renderer_get_instance_private(self);
|
||||||
|
|
||||||
|
priv->layer_settings = NULL;
|
||||||
priv->output_file = NULL;
|
priv->output_file = NULL;
|
||||||
|
priv->thread = NULL;
|
||||||
|
priv->mutex_init_status = TRUE;
|
||||||
|
g_mutex_init(&priv->settings_lock);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,11 +224,23 @@ const char *gds_output_renderer_get_output_file(GdsOutputRenderer *renderer)
|
|||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
LayerSettings *gds_output_renderer_get_layer_settings(GdsOutputRenderer *renderer)
|
LayerSettings *gds_output_renderer_get_and_ref_layer_settings(GdsOutputRenderer *renderer)
|
||||||
{
|
{
|
||||||
LayerSettings *ret = NULL;
|
LayerSettings *ret = NULL;
|
||||||
|
GdsOutputRendererPrivate *priv;
|
||||||
|
|
||||||
|
priv = gds_output_renderer_get_instance_private(renderer);
|
||||||
|
|
||||||
|
/* Acquire settings lock */
|
||||||
|
g_mutex_lock(&priv->settings_lock);
|
||||||
|
|
||||||
g_object_get(renderer, "layer-settings", &ret, NULL);
|
g_object_get(renderer, "layer-settings", &ret, NULL);
|
||||||
|
/* Reference it, so it is not cleared by another thread overwriting the property */
|
||||||
|
g_object_ref(ret);
|
||||||
|
|
||||||
|
/* It is now safe to clear the lock */
|
||||||
|
g_mutex_unlock(&priv->settings_lock);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,6 +253,7 @@ void gds_output_renderer_set_layer_settings(GdsOutputRenderer *renderer, LayerSe
|
|||||||
|
|
||||||
int gds_output_renderer_render_output(GdsOutputRenderer *renderer, struct gds_cell *cell, double scale)
|
int gds_output_renderer_render_output(GdsOutputRenderer *renderer, struct gds_cell *cell, double scale)
|
||||||
{
|
{
|
||||||
|
int ret;
|
||||||
GdsOutputRendererClass *klass;
|
GdsOutputRendererClass *klass;
|
||||||
GdsOutputRendererPrivate *priv = gds_output_renderer_get_instance_private(renderer);
|
GdsOutputRendererPrivate *priv = gds_output_renderer_get_instance_private(renderer);
|
||||||
|
|
||||||
@ -228,8 +283,40 @@ int gds_output_renderer_render_output(GdsOutputRenderer *renderer, struct gds_ce
|
|||||||
return GDS_OUTPUT_RENDERER_GEN_ERR;
|
return GDS_OUTPUT_RENDERER_GEN_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
return klass->render_output(renderer, cell, scale);
|
ret = klass->render_output(renderer, cell, scale);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int gds_output_renderer_async_wrapper(gpointer data)
|
||||||
|
{
|
||||||
|
GdsOutputRenderer *renderer;
|
||||||
|
GdsOutputRendererPrivate *priv;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
renderer = GDS_RENDER_OUTPUT_RENDERER(data);
|
||||||
|
priv = gds_output_renderer_get_instance_private(renderer);
|
||||||
|
if (!priv)
|
||||||
|
return -1000;
|
||||||
|
|
||||||
|
if(!priv->mutex_init_status)
|
||||||
|
return -1001;
|
||||||
|
|
||||||
|
g_mutex_lock(&priv->settings_lock);
|
||||||
|
ret = gds_output_renderer_render_output(renderer, priv->async_params.cell, priv->async_params.scale);
|
||||||
|
g_mutex_unlock(&priv->settings_lock);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gds_output_renderer_render_output_async(GdsOutputRenderer *renderer, struct gds_cell *cell, double scale)
|
||||||
|
{
|
||||||
|
GdsOutputRendererPrivate *priv;
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
priv = gds_output_renderer_get_instance_private(renderer);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
||||||
|
@ -335,7 +335,7 @@ static int latex_renderer_render_output(GdsOutputRenderer *renderer,
|
|||||||
const char *output_file;
|
const char *output_file;
|
||||||
|
|
||||||
output_file = gds_output_renderer_get_output_file(renderer);
|
output_file = gds_output_renderer_get_output_file(renderer);
|
||||||
settings = gds_output_renderer_get_layer_settings(renderer);
|
settings = gds_output_renderer_get_and_ref_layer_settings(renderer);
|
||||||
|
|
||||||
/* Set layer info list. In case of failure it remains NULL */
|
/* Set layer info list. In case of failure it remains NULL */
|
||||||
if (settings)
|
if (settings)
|
||||||
@ -350,6 +350,10 @@ static int latex_renderer_render_output(GdsOutputRenderer *renderer,
|
|||||||
g_error("Could not open LaTeX output file");
|
g_error("Could not open LaTeX output file");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (settings) {
|
||||||
|
g_object_unref(settings);
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user