Commit 0c3a47fa authored by Pierre Wieser's avatar Pierre Wieser

Dynamically generate configurations schemas

parent c7c1d453
2009-03-17 Pierre Wieser <pwieser@trychlos.org>
* data/Makefile.am: Automatically generate configurations schemas.
* data/nautilus-actions.schemas.in:
Renamed as data/nautilus-actions-prefs.schemas.in.
Remove configurations schemas (now dynamically generated).
* po/POTFILES.in: Updated accordingly.
* src/core/na-icontextual-factory.c:
* src/core/na-object-action-factory.c: Fix typo.
* src/utils/nautilus-actions-schemas.c:
Output configurations schemas to stdout.
* po/POTFILES.skip:
New file: do not try to translate dynamically generated schema.
* Makefile.am:
Build data/ after src/ to bo take advantage of dynamic generation of schemas.
......
......@@ -51,11 +51,6 @@
- undo manager (at least Ctrl-Z)
- have a single place where a schema is written
(see src/utils/nautilus-actions-schemas.c)
more: have a single place where the schema is _described_
so that we may generalize export, import, new, etc.
- IActionTab: check_label has became useless since validity is global
to the action - remove, or extend to each field which can led to
unvalidity
......
......@@ -26,10 +26,16 @@
# Pierre Wieser <pwieser@trychlos.org>
# ... and many others (see AUTHORS)
schemas_in_files = nautilus-actions.schemas.in
schemas_in_files = \
nautilus-actions-prefs.schemas.in \
nautilus-actions-confs.schemas.in \
$(NULL)
schemas_files = $(schemas_in_files:.schemas.in=.schemas)
nautilus-actions-confs.schemas.in:
$(top_srcdir)/src/utils/nautilus-actions-schemas -s > $@
schemasdir = $(pkgdatadir)
schemas_DATA = $(schemas_files)
......@@ -43,7 +49,7 @@ NA_INSTALL_SCHEMAS = no
endif
EXTRA_DIST = \
$(schemas_in_files) \
$(schemas_in_files) \
$(NULL)
install-data-hook:
......
......@@ -2,319 +2,6 @@
<gconfschemafile>
<schemalist>
<!-- schemas for action entries -->
<!-- actions should have a 'schemakey' tag to point to these schemas -->
<schema>
<key>/schemas/apps/nautilus-actions/configurations/version</key>
<owner>nautilus-actions</owner>
<type>string</type>
<locale name="C">
<short>The version of the configuration format</short>
<long>The version of the configuration format that will be used to manage backward compatibility.</long>
</locale>
<default>2.0</default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/label</key>
<owner>nautilus-actions</owner>
<type>string</type>
<locale name="C">
<default></default>
<short>The label of the menu item</short>
<long>The label of the menu item that will appear in the Nautilus popup menu when the selection matches the appearance condition settings.</long>
</locale>
<default></default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/tooltip</key>
<owner>nautilus-actions</owner>
<type>string</type>
<locale name="C">
<default></default>
<short>The tooltip of the menu item</short>
<long>The tooltip of the menu item that will appear in the Nautilus statusbar when the user points to the Nautilus popup menu item with his/her mouse.</long>
</locale>
<default></default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/icon</key>
<owner>nautilus-actions</owner>
<type>string</type>
<locale name="C">
<short>The icon of the menu item</short>
<long>The icon of the menu item that will appear next to the label in the Nautilus popup menu when the selection matches the appearance conditions settings.</long>
</locale>
<default></default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/enabled</key>
<owner>nautilus-actions</owner>
<type>bool</type>
<locale name="C">
<short>Whether the action is enabled</short>
<long>If the action is disabled, it will never appear in the Nautilus context menu.</long>
</locale>
<default>true</default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/target_selection</key>
<owner>nautilus-actions</owner>
<type>bool</type>
<locale name="C">
<short>Targets selection</short>
<long>Whether the action of menu targets the selection Nautilus menus. This is the historical behavior.</long>
</locale>
<default>true</default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/target_toolbar</key>
<owner>nautilus-actions</owner>
<type>bool</type>
<locale name="C">
<short>Targets toolbar</short>
<long>Whether the action is candidate to be displayed in Nautilus toolbar.
Note, that as of Nautilus 2.26, menus can not be candidate to toolbar display.</long>
</locale>
<default>false</default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/items</key>
<owner>nautilus-actions</owner>
<type>list</type>
<list_type>string</list_type>
<locale name="C">
<short>List of subitem ids</short>
<long>Ordered list of the IDs of the subitems. This may be actions or menus if the item is a menu, or profiles if the item is an action.
If this list doesn't exist or is empty for an action, we load the found profiles in the order of the read operations.</long>
</locale>
<default>[]</default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/type</key>
<owner>nautilus-actions</owner>
<type>string</type>
<locale name="C">
<short>Type of the item</short>
<long>Defines if the item is an action or a menu. Possible values are :
- "Action",
- "Menu".
The value is case sensitive and must not be localized.</long>
</locale>
<default>Action</default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/toolbar-label</key>
<owner>nautilus-actions</owner>
<type>bool</type>
<locale name="C">
<short>Label of the item in the toolbar</short>
<long>The label displayed besides of the icon in the Nautilus toolbar.
An empty label here defaults to the item label.
Note that actual display (only icon, only label or both) may depend of your own Gnome preferences.</long>
</locale>
<default></default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/desc-name</key>
<owner>nautilus-actions</owner>
<type>string</type>
<locale name="C">
<default>Default profile</default>
<short>A description name of the profile</short>
<long>The field is here to give the user a human readable name for a profile in the NACT interface. If not set there will be a default auto generated string set.</long>
</locale>
<default>Default profile</default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/path</key>
<owner>nautilus-actions</owner>
<type>string</type>
<locale name="C">
<short>The path of the command</short>
<long>The path of the command to start when the user select the menu item in the Nautilus popup menu.</long>
</locale>
<default></default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/parameters</key>
<owner>nautilus-actions</owner>
<type>string</type>
<locale name="C">
<short>The parameters of the command</short>
<long>The parameters of the command to start when the user selects the menu item in the Nautilus popup menu.
The parameters can contain some special tokens which are replaced by Nautilus information before starting the command:
%d: base folder of the selected file(s)
%f: the name of the selected file or the first one if many are selected
%h: hostname of the URI
%m: space-separated list of the basenames of the selected file(s)/folder(s)
%M: space-separated list of the selected file(s)/folder(s), with their full paths
%p: port number of the first URI
%R: space-separated list of selected URIs
%s: scheme of the URI
%u: URI
%U: username of the URI
%%: a percent sign.</long>
</locale>
<default></default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/basenames</key>
<owner>nautilus-actions</owner>
<type>list</type>
<list_type>string</list_type>
<locale name="C">
<short>The list of pattern to match the selected file(s)/folder(s)</short>
<long>A list of strings with joker '*' or '?' to match the name of the selected file(s)/folder(s). Each selected items must match at least one of the filename patterns for the action to appear. This only applies when targeting selection.</long>
</locale>
<default>[*]</default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/matchcase</key>
<owner>nautilus-actions</owner>
<type>bool</type>
<locale name="C">
<short>'true' if the filename patterns have to be case sensitive, 'false' otherwise</short>
<long>If you need to match a filename in a case-sensitive manner, set this key to 'true'. If you also want, for example '*.jpg' to match 'photo.JPG', set 'false'. This only applies when targeting selection.</long>
</locale>
<default>true</default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/mimetypes</key>
<owner>nautilus-actions</owner>
<type>list</type>
<list_type>string</list_type>
<locale name="C">
<short>The list of patterns to match the mimetypes of the selected file(s)</short>
<long>A list of strings with joker '*' or '?' to match the mimetypes of the selected file(s). Each selected items must match at least one of the mimetype patterns for the action to appear. This only applies when targeting selection.</long>
</locale>
<default>[*/*]</default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/isfile</key>
<owner>nautilus-actions</owner>
<type>bool</type>
<locale name="C">
<short>'true' if the selection can have files, 'false' otherwise</short>
<long>This setting is tied in with the 'isdir' setting. The valid combinations are :
- isfile=TRUE and isdir=FALSE: the selection may hold only files
- isfile=FALSE and isdir=TRUE: the selection may hold only folders
- isfile=TRUE and isdir=TRUE: the selection may hold both files and folders
- isfile=FALSE and isdir=FALSE: this is an invalid combination (your configuration will never appear).
This only applies when targeting selection.</long>
</locale>
<default>true</default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/isdir</key>
<owner>nautilus-actions</owner>
<type>bool</type>
<locale name="C">
<short>'true' if the selection can have folders, 'false' otherwise</short>
<long>This setting is tied in with the 'isfile' setting. The valid combinations are :
- isfile=TRUE and isdir=FALSE: the selection may hold only files
- isfile=FALSE and isdir=TRUE: the selection may hold only folders
- isfile=TRUE and isdir=TRUE: the selection may hold both files and folders
- isfile=FALSE and isdir=FALSE: this is an invalid combination (your configuration will never appear).
This only applies when targeting selection.</long>
</locale>
<default>false</default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/accept-multiple-files</key>
<owner>nautilus-actions</owner>
<type>bool</type>
<locale name="C">
<short>'true' if the selection can have several items, 'false' otherwise</short>
<long>If you need one or more files or folders to be selected, set this key to 'true'. If you want just one file or folder, set 'false'. This only applies when targeting selection.</long>
</locale>
<default>false</default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/schemes</key>
<owner>nautilus-actions</owner>
<type>list</type>
<list_type>string</list_type>
<locale name="C">
<short>The list of schemes where the selected files should be located</short>
<long>Defines the list of valid schemes to be matched against the selected items. The scheme is the protocol used to access the files. The keyword to use is the one used in the URI.
Examples of valid URI include :
- file:///tmp/foo.txt
- sftp:///root@test.example.net/tmp/foo.txt
The most common schemes are :
- 'file': local files
- 'sftp': files accessed via SSH
- 'ftp' : files accessed via FTP
- 'smb' : files accessed via Samba (Windows share)
- 'dav' : files accessed via WebDAV
All schemes used by Nautilus can be used here. This only applies when targeting selection.</long>
</locale>
<default>[file]</default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/configurations/folders</key>
<owner>nautilus-actions</owner>
<type>list</type>
<list_type>string</list_type>
<locale name="C">
<short>The list of URIs on which the Background item applies</short>
<long>Defines the list of valid URIs to be matched against the current folder. All folders 'under' the specified URI are considered valid.</long>
</locale>
<default>[]</default>
</schema>
<!-- schemas for io-providers -->
<schema>
<key>/schemas/apps/nautilus-actions/io-providers/read-at-startup</key>
<owner>nautilus-actions</owner>
<type>bool</type>
<locale name="C">
<short>Whether this I/O provider should be read at startup</short>
<long>When set to false, this prevents this whole I/O provider to be read at startup.
Actual key is /apps/nautilus-actions/io-providers/<provider_id>/read-at-startup.
This is a user preference.</long>
</locale>
<default>true</default>
</schema>
<schema>
<key>/schemas/apps/nautilus-actions/io-providers/writable</key>
<owner>nautilus-actions</owner>
<type>bool</type>
<locale name="C">
<short>Whether this I/O provider is writable</short>
<long>When set to false, this prevents all items readen from this I/O provider from being updated in NACT.
Actual key is /apps/nautilus-actions/io-providers/<provider_id>/writable.
This is a user preference.</long>
</locale>
<default>true</default>
</schema>
<!-- schemas for preferences -->
<schema>
......
data/nautilus-actions.schemas.in
data/nautilus-actions-prefs.schemas.in
src/core/na-exporter.c
src/core/na-iabout.c
src/core/na-icontextual-factory.c
......
data/nautilus-actions-confs.schemas.in
......@@ -240,7 +240,7 @@ NADataDef data_def_conditions [] = {
N_( "List of folders" ),
N_( "Defines the list of valid paths to be matched against the current folder.\n " \
"All folders 'under' the specified path are considered valid.\n" \
"This is only used when there is no selection.\n" \
"This is only used when there is no selection, or the selection only contains directories.\n" \
"Defaults to '/'." ),
NAFD_TYPE_STRING_LIST,
"[/]",
......@@ -253,7 +253,7 @@ NADataDef data_def_conditions [] = {
"folder",
0,
G_OPTION_ARG_STRING,
N_( "The path of a directory for which the item will be displayed. " \
N_( "The path of a (parent) directory for which the item will be displayed. " \
"You must set one option for each folder you need" ),
N_( "<PATH>" ) },
......
......@@ -122,7 +122,7 @@ static NADataDef data_def_action [] = {
TRUE,
N_( "Targets the toolbar" ),
N_( "Whether the action is candidate to be displayed in file manager toolbar.\n" \
"Note, that as of Nautilus 2.26, menus can not be candidate to toolbar display." ),
"Note, that as of Nautilus 2.26, menus cannot be candidate to toolbar display." ),
NAFD_TYPE_BOOLEAN,
"false",
TRUE,
......
......@@ -32,45 +32,69 @@
#include <config.h>
#endif
#include <gconf/gconf.h>
#include <gconf/gconf-client.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <glib/gprintf.h>
#include <libxml/tree.h>
#include <stdlib.h>
#include <api/na-core-utils.h>
#include <api/na-data-types.h>
#include <api/na-ifactory-object-data.h>
#include <core/na-iprefs.h>
#if 0
#include <core/na-xml-names.h>
#include <core/na-xml-writer.h>
#endif
#include <io-gconf/nagp-keys.h>
#include <io-xml/naxml-keys.h>
#include "console-utils.h"
/*static gchar *output_fname = NULL;
static gboolean output_gconf = FALSE;*/
typedef struct {
NADataGroup *group;
gchar *group_name;
gchar *data_name;
}
SchemaFromDataDef;
extern NADataGroup action_data_groups[]; /* defined in na-object-action-factory.c */
extern NADataGroup profile_data_groups[]; /* defined in na-object-profile-factory.c */
static const SchemaFromDataDef st_schema_from_data_def[] = {
{ action_data_groups, NA_FACTORY_OBJECT_ITEM_GROUP, NAFO_DATA_LABEL },
{ action_data_groups, NA_FACTORY_OBJECT_ITEM_GROUP, NAFO_DATA_TOOLTIP },
{ action_data_groups, NA_FACTORY_OBJECT_ITEM_GROUP, NAFO_DATA_ICON },
{ action_data_groups, NA_FACTORY_OBJECT_ITEM_GROUP, NAFO_DATA_ENABLED },
{ action_data_groups, NA_FACTORY_OBJECT_ACTION_GROUP, NAFO_DATA_TARGET_SELECTION },
{ action_data_groups, NA_FACTORY_OBJECT_ACTION_GROUP, NAFO_DATA_TARGET_TOOLBAR },
{ action_data_groups, NA_FACTORY_OBJECT_ACTION_GROUP, NAFO_DATA_TOOLBAR_LABEL },
{ profile_data_groups, NA_FACTORY_OBJECT_PROFILE_GROUP, NAFO_DATA_PATH },
{ profile_data_groups, NA_FACTORY_OBJECT_PROFILE_GROUP, NAFO_DATA_PARAMETERS },
{ profile_data_groups, NA_FACTORY_OBJECT_CONDITIONS_GROUP, NAFO_DATA_BASENAMES },
{ profile_data_groups, NA_FACTORY_OBJECT_CONDITIONS_GROUP, NAFO_DATA_MATCHCASE },
{ profile_data_groups, NA_FACTORY_OBJECT_CONDITIONS_GROUP, NAFO_DATA_MIMETYPES },
{ profile_data_groups, NA_FACTORY_OBJECT_CONDITIONS_GROUP, NAFO_DATA_ISFILE },
{ profile_data_groups, NA_FACTORY_OBJECT_CONDITIONS_GROUP, NAFO_DATA_ISDIR },
{ profile_data_groups, NA_FACTORY_OBJECT_CONDITIONS_GROUP, NAFO_DATA_MULTIPLE },
{ profile_data_groups, NA_FACTORY_OBJECT_CONDITIONS_GROUP, NAFO_DATA_SCHEMES },
{ profile_data_groups, NA_FACTORY_OBJECT_CONDITIONS_GROUP, NAFO_DATA_FOLDERS },
{ NULL }
};
static gboolean output_stdout = FALSE;
static gboolean version = FALSE;
static GOptionEntry entries[] = {
/*{ "output-gconf" , 'g', 0, G_OPTION_ARG_NONE , &output_gconf , N_("Writes the Nautilus Actions schema in GConf"), NULL },
{ "output-filename" , 'o', 0, G_OPTION_ARG_FILENAME, &output_fname , N_("The file where to write the GConf schema ('-' for stdout)"), N_("FILENAME") },*/
{ "stdout", 's', 0, G_OPTION_ARG_NONE, &output_stdout, N_("Output the schema on stdout"), NULL },
{ "stdout" , 's', 0, G_OPTION_ARG_NONE, &output_stdout, N_("Output the schema on stdout"), NULL },
{ NULL }
};
static GOptionEntry misc_entries[] = {
{ "version" , 'v', 0, G_OPTION_ARG_NONE , &version,
N_("Output the version number"), NULL },
{ "version", 'v', 0, G_OPTION_ARG_NONE, &version, N_("Output the version number"), NULL },
{ NULL }
};
static GOptionContext *init_options( void );
/*static gboolean write_to_gconf( gchar **msg );
static gboolean write_schema( GConfClient *gconf, const gchar *prefix, GConfValueType type, const gchar *entry, const gchar *dshort, const gchar *dlong, const gchar *default_value, gchar **msg );*/
static int output_to_stdout( GSList **msgs );
static void attach_schema_node( xmlDocPtr doc, xmlNodePtr list_node, const NADataDef *data_def );
static void exit_with_usage( void );
int
......@@ -80,7 +104,7 @@ main( int argc, char** argv )
GOptionContext *context;
gchar *help;
GError *error = NULL;
GSList *msg = NULL;
GSList *msgs = NULL;
GSList *im;
g_type_init();
......@@ -107,29 +131,15 @@ main( int argc, char** argv )
exit( status );
}
/*if( output_gconf && output_fname ){
g_printerr( _( "Error: only one output option may be specified." ));
exit_with_usage();
}*/
/*if( output_gconf ){
if( write_to_gconf( &msg )){
g_print( _( "Nautilus Actions schema succesfully written to GConf.\n" ));
}
if( output_stdout ){
status = output_to_stdout( &msgs );
}
} else {
na_xml_writer_export( NULL, NULL, IPREFS_EXPORT_FORMAT_GCONF_SCHEMA, &msg );
if( !msg ){
g_print( _( "Nautilus Actions schema succesfully written to %s.\n" ), output_fname );
g_free( output_fname );
}*/
/*}*/
if( msg ){
for( im = msg ; im ; im = im->next ){
if( msgs ){
for( im = msgs ; im ; im = im->next ){
g_printerr( "%s\n", ( gchar * ) im->data );
}
na_core_utils_slist_free( msg );
na_core_utils_slist_free( msgs );
status = EXIT_FAILURE;
}
......@@ -149,9 +159,6 @@ init_options( void )
GOptionGroup *misc_group;
context = g_option_context_new( _( "Output the Nautilus Actions GConf schema on stdout." ));
/*" The schema can be written to stdout.\n"
" It can also be written to an output file, in a file later suitable for an installation via gconftool-2.\n"
" Or you may choose to directly write the schema into the GConf configuration." ));*/
#ifdef ENABLE_NLS
bindtextdomain( GETTEXT_PACKAGE, GNOMELOCALEDIR );
......@@ -275,6 +282,83 @@ write_schema( GConfClient *gconf, const gchar *prefix, GConfValueType type, cons
return( ret );
}*/
static int
output_to_stdout( GSList **msgs )
{
static const gchar *thisfn = "nautilus_actions_schemas_output_to_stdout";
xmlDocPtr doc;
xmlNodePtr root_node;
xmlNodePtr list_node;
xmlChar *text;
int textlen;
SchemaFromDataDef *isch;
const NADataDef *data_def;
doc = xmlNewDoc( BAD_CAST( "1.0" ));
root_node = xmlNewNode( NULL, BAD_CAST( NAXML_KEY_SCHEMA_ROOT ));
xmlDocSetRootElement( doc, root_node );
list_node = xmlNewChild( root_node, NULL, BAD_CAST( NAXML_KEY_SCHEMA_LIST ), NULL );
isch = ( SchemaFromDataDef * ) st_schema_from_data_def;
while( isch->group ){
data_def = na_data_def_get_data_def( isch->group, isch->group_name, isch->data_name );
if( data_def ){
attach_schema_node( doc, list_node, data_def );
} else {
g_warning( "%s: group=%s, name=%s: unable to find NADataDef structure", thisfn, isch->group_name, isch->data_name );
}
isch++;
}
xmlDocDumpFormatMemoryEnc( doc, &text, &textlen, "UTF-8", 1 );
g_printf( "%s\n", ( const char * ) text );
xmlFree( text );
xmlFreeDoc (doc);
xmlCleanupParser();
return( EXIT_SUCCESS );
}
static void
attach_schema_node( xmlDocPtr doc, xmlNodePtr list_node, const NADataDef *def )
{
xmlNodePtr schema_node;
xmlChar *content;
xmlNodePtr parent_value_node;
xmlNodePtr locale_node;
schema_node = xmlNewChild( list_node, NULL, BAD_CAST( NAXML_KEY_SCHEMA_NODE ), NULL );
content = BAD_CAST( g_build_path( "/", NAGP_SCHEMAS_PATH, def->gconf_entry, NULL ));
xmlNewChild( schema_node, NULL, BAD_CAST( NAXML_KEY_SCHEMA_NODE_KEY ), content );
xmlFree( content );
xmlNewChild( schema_node, NULL, BAD_CAST( NAXML_KEY_SCHEMA_NODE_OWNER ), BAD_CAST( PACKAGE_TARNAME ));
xmlNewChild( schema_node, NULL, BAD_CAST( NAXML_KEY_SCHEMA_NODE_TYPE ), BAD_CAST( na_data_types_get_gconf_dump_key( def->type )));
if( def->type == NAFD_TYPE_STRING_LIST ){
xmlNewChild( schema_node, NULL, BAD_CAST( NAXML_KEY_SCHEMA_NODE_LISTTYPE ), BAD_CAST( "string" ));
}
locale_node = xmlNewChild( schema_node, NULL, BAD_CAST( NAXML_KEY_SCHEMA_NODE_LOCALE ), NULL );
xmlNewProp( locale_node, BAD_CAST( "name" ), BAD_CAST( "C" ));
xmlNewChild( locale_node, NULL, BAD_CAST( NAXML_KEY_SCHEMA_NODE_LOCALE_SHORT ), BAD_CAST( def->short_label ));
xmlNewChild( locale_node, NULL, BAD_CAST( NAXML_KEY_SCHEMA_NODE_LOCALE_LONG ), BAD_CAST( def->long_label ));
parent_value_node = def->localizable ? locale_node : schema_node;
content = xmlEncodeSpecialChars( doc, BAD_CAST( def->default_value ));
xmlNewChild( parent_value_node, NULL, BAD_CAST( NAXML_KEY_SCHEMA_NODE_DEFAULT ), content );
xmlFree( content );
}
/*
* print a help message and exit with failure
*/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment