Commit bc7d900b authored by claes's avatar claes

New package handler

parent 77685925
#!/bin/bash
pkg_install_func ()
{
if [ ! -e "$1" ]; then
echo "No package $1"
exit 1
fi
pkg=`eval locate $1`
pkg_remove_func "force"
echo "-- Installing package $1"
cd /tmp
if ! tar -xzf $pkg pkg_unpack.sh; then
echo "Package $1 incomplete"
exit 1
fi
chmod a+x pkg_unpack.sh
./pkg_unpack.sh
}
pkg_list_func ()
{
if [ -z $1 ]; then
# List installed package
if [ ! -e $pwrp_load/pwr_pkg.dat ]; then
echo "-- No package installed"
exit 1
fi
datfile=$pwrp_load/pwr_pkg.dat
else
# Extract datfile from package
if [ ! -e $1 ]; then
echo "-- No such package"
exit 1
fi
pkg=`eval locate $1`
echo "-- Opening file $pkg"
cd /tmp
if ! tar -xzf $pkg pwr_pkg.dat; then
echo "Package $1 incomplete"
exit 1
fi
datfile=/tmp/pwr_pkg.dat
fi
{
let printout=0
while read line; do
if [ "$line" = "%Description:" ]; then
printout=1
else
if [ "$line" = "%Files:" ]; then
break
fi
if [ $printout -eq 1 ]; then
echo $line
fi
fi
done
} < $datfile
}
pkg_listfiles_func ()
{
if [ -z $1 ]; then
# List installed package
if [ ! -e $pwrp_load/pwr_pkg.dat ]; then
echo "-- No package installed"
exit 1
fi
datfile=$pwrp_load/pwr_pkg.dat
else
# Extract datfile from package
if [ ! -e $1 ]; then
echo "-- No such package"
exit 1
fi
pkg=`eval locate $1`
echo "-- Opening file $pkg"
cd /tmp
if ! tar -xzf $pkg pwr_pkg.dat; then
echo "Package $1 incomplete"
exit 1
fi
datfile=/tmp/pwr_pkg.dat
fi
{
let printout=0
while read line date time; do
if [ "$line" = "%Files:" ]; then
printout=1
else
if [ "$line" = "%End:" ]; then
break
fi
if [ $printout -eq 1 ]; then
echo $date $time $line
fi
fi
done
} < $datfile
}
pkg_brief_func ()
{
if [ -z $1 ]; then
# List installed package
if [ ! -e $pwrp_load/pwr_pkg.dat ]; then
echo "-- No package installed"
exit 1
fi
datfile=$pwrp_load/pwr_pkg.dat
{
let printout=0
while read line; do
if [ "$line" = "%Brief:" ]; then
printout=1
else
if [ "$line" = "%Description:" ]; then
break
fi
if [ $printout -eq 1 ]; then
echo $line
fi
fi
done
} < $datfile
else
# Extract datfile from package
for file
do
if [ ! -e $file ]; then
echo "-- No such package"
exit 1
fi
pkg=`eval locate $file`
dir=`eval pwd`
cd /tmp
if ! tar -xzf $pkg pwr_pkg.dat; then
echo "Package $pkg incomplete"
else
datfile=/tmp/pwr_pkg.dat
{
let printout=0
while read line; do
if [ "$line" = "%Brief:" ]; then
printout=1
else
if [ "$line" = "%Description:" ]; then
break
fi
if [ $printout -eq 1 ]; then
echo $line
fi
fi
done
} < $datfile
fi
cd $dir
done
fi
}
pkg_remove_func ()
{
if [ ! -e $pwrp_load/pwr_pkg.dat ]; then
echo "-- No package installed"
return
fi
# Get the name of the current package
{
let found=0
while read line; do
if [ "$line" = "%Package:" ]; then
found=1
else
if [ $found -eq 1 ]; then
pkg=$line
break
fi
fi
done
} < $pwrp_load/pwr_pkg.dat
if [ ! $1 = "force" ]; then
echo ""
echo -n "Do you wan't to remove package $pkg (y/n) [n] "
read remove_pkg
if [ ! "$remove_pkg" = "y" ]; then
return
fi
fi
echo "-- Removing package $pkg"
{
let removefile=0
while read line date time; do
if [ "$line" = "%Files:" ]; then
removefile=1
else
if [ "$line" = "%End:" ]; then
break
fi
if [ $removefile -eq 1 ]; then
file=`eval echo $line`
#echo "rm $file"
rm $file
fi
fi
done
} < $pwrp_load/pwr_pkg.dat
rm $pwrp_load/pwr_pkg.dat
}
force="no"
while getopts ":i:l:b:fra:" opt; do
case $opt in
i ) pkg_install_func $OPTARG ;;
l ) pkg_list_func $OPTARG ;;
a ) pkg_listfiles_func $OPTARG;;
b ) shift
pkg_brief_func $@ ;;
r ) pkg_remove_func $force ;;
f ) force="force" ;;
\? ) echo 'usage: pwr_pkg [-i pkg] [-l] [-b] [-r]'
exit 1
esac
done
! pwrb_c_distribute.wb_load -- Defines the class Distribute.
!
! PROVIEW/R
! Copyright (C) 1996 by Comator Process AB.
!
! <Description>.
!
!/**
! @Version 1.0
! @Group ProjectConfiguration
! @Summary Configures distribution of files.
! The Distribute object configures distribution of files to process and
! operator stations.
!
! The object reside in the project volume beneath a NodeConfig object.
! The specified file will be copied to the node configured by the
! NodeConfig object.
!
! @b See also
! @classlink NodeConfig pwrb_nodeconfig.html
! @classlink ApplDistribute pwrb_appldistribute.html
!*/
SObject pwrb:Class
Object Distribute $ClassDef 321
Body SysBody
Attr Editor = pwr_eEditor_AttrEd
Attr Method = pwr_eMethod_RtAndDevBodies
EndBody
Object DevBody $ObjBodyDef 2
!/**
! @Summary Specification of components included in the distribution.
! Specification of components that are included in the distribution
! package.
!*/
Object Components $Attribute 1
Body SysBody
Attr TypeRef = "pwrs:Type-$Mask"
EndBody
EndObject
EndObject
EndObject
EndSObject
...@@ -12,11 +12,30 @@ ...@@ -12,11 +12,30 @@
#include "wb_ldh.h" #include "wb_ldh.h"
#endif #endif
#ifdef __cplusplus
extern "C" {
#endif
typedef enum { typedef enum {
lfu_eDistrSts_Normal = 0, lfu_eDistrSts_Normal = 0,
lfu_eDistrSts_NoRootVolume = 1 << 0 lfu_eDistrSts_NoRootVolume = 1 << 0
} lfu_eDistrSts; } lfu_eDistrSts;
typedef enum {
lfu_mDistrComponents_UserDatabase = 1 << 0,
lfu_mDistrComponents_LoadFiles = 1 << 1,
lfu_mDistrComponents_ApplFile = 1 << 2,
lfu_mDistrComponents_PwrpAliasFile = 1 << 3,
lfu_mDistrComponents_IncludeFiles = 1 << 4,
lfu_mDistrComponents_GraphFiles = 1 << 5,
lfu_mDistrComponents_XttHelpFile = 1 << 6,
lfu_mDistrComponents_XttResourceFile = 1 << 7,
lfu_mDistrComponents_XttSetupFile = 1 << 8,
lfu_mDistrComponents_FlowFiles = 1 << 9,
lfu_mDistrComponents_RHostsFile = 1 << 10,
lfu_mDistrComponents_WebFiles = 1 << 11
} lfu_mDistrComponents;
typedef enum { typedef enum {
lfu_eAccessType_StdLgi = 0, lfu_eAccessType_StdLgi = 0,
lfu_eAccessType_Proxy = 1, lfu_eAccessType_Proxy = 1,
...@@ -106,4 +125,8 @@ pwr_tStatus lfu_WriteSysObjectFile( ...@@ -106,4 +125,8 @@ pwr_tStatus lfu_WriteSysObjectFile(
ldh_tSesContext ldhses ldh_tSesContext ldhses
); );
#ifdef __cplusplus
}
#endif
#endif #endif
#include <iostream>
#include <fstream>
#include <sys/stat.h>
#include "wb_pkg.h"
#include "wb_error.h"
#include "co_msgwindow.h"
#include "co_dcli.h"
#include "co_cdh.h"
#include "co_time.h"
#include "rt_load.h"
wb_pkg::wb_pkg( char *nodelist)
{
if ( nodelist) {
char node_str[32][20];
int num;
cdh_ToLower( nodelist, nodelist);
num = dcli_parse( nodelist, " ,", "", (char *)node_str,
sizeof(node_str)/sizeof(node_str[0]),
sizeof(node_str[0]), 0);
m_allnodes = false;
for ( int i = 0; i < num; i++) {
pkg_node n( node_str[i]);
m_nodelist.push_back( n);
}
}
else
m_allnodes = true;
readConfig();
for ( int i = 0; i < (int)m_nodelist.size(); i++)
printf( "%d %s\n", i, m_nodelist[i].name());
fetchFiles();
}
void wb_pkg::readConfig()
{
char fname[200];
char line[200];
char line_item[6][80];
int num;
int sts;
dcli_translate_filename( fname, load_cNameDistribute);
ifstream is( fname);
while ( is.getline( line, sizeof(line))) {
dcli_trim( line, line);
if ( line[0] == '#' || line[0] == '!')
continue;
num = dcli_parse( line, " ", "", (char *)line_item,
sizeof(line_item)/sizeof(line_item[0]),
sizeof(line_item[0]), 0);
if ( !num)
continue;
if ( strcmp( cdh_Low(line_item[0]), "node") == 0) {
pwr_mOpSys opsys;
int bus;
lfu_eDistrSts dstatus;
if ( num != 5)
throw wb_error_str("File corrupt " load_cNameDistribute);
sts = sscanf( line_item[2], "%d", (int *)&opsys);
if ( sts != 1)
throw wb_error_str("File corrupt " load_cNameDistribute);
sts = sscanf( line_item[3], "%d", &bus);
if ( sts != 1)
throw wb_error_str("File corrupt " load_cNameDistribute);
sts = sscanf( line_item[4], "%d", (int *)&dstatus);
if ( sts != 1)
throw wb_error_str("File corrupt " load_cNameDistribute);
if ( m_allnodes) {
pkg_node node( line_item[1], opsys, bus, dstatus);
m_nodelist.push_back( node);
}
else {
bool found = false;
for ( int i = 0; i < (int) m_nodelist.size(); i++) {
if ( strcmp( m_nodelist[i].name(), cdh_Low(line_item[1])) == 0) {
found = true;
m_nodelist[i].setOpsys( opsys);
m_nodelist[i].setBus( bus);
m_nodelist[i].setDStatus( dstatus);
m_nodelist[i].setValid();
break;
}
}
}
}
else if ( strcmp( cdh_Low(line_item[0]), "appl") == 0) {
if ( !(num == 3 || num == 4))
throw wb_error_str("File corrupt " load_cNameDistribute);
try {
pkg_node& n = getNode( line_item[1]);
if ( num == 3) {
pkg_pattern p( line_item[2]);
n.push_back( p);
}
else {
pkg_pattern p( line_item[2], line_item[3]);
n.push_back( p);
}
} catch ( wb_error &e) {
continue;
}
}
}
is.close();
// Check that all nodes are valid
for ( int i = 0; i < (int) m_nodelist.size(); i++) {
if ( !m_nodelist[i].valid())
throw wb_error_str( "Unknown node name");
}
}
pkg_node& wb_pkg::getNode( char *name)
{
for ( int i = 0; i < (int) m_nodelist.size(); i++) {
if ( strcmp( m_nodelist[i].name(), cdh_Low(name)) == 0)
return m_nodelist[i];
}
throw wb_error_str("No such node");
}
void pkg_node::fetchFiles()
{
char dev[80];
char dir[80];
char file[80];
char type[80];
int version;
char pack_fname[200];
char fname[200];
for ( int i = 0; i < (int)m_pattern.size(); i++)
m_pattern[i].fetchFiles();
// Put all files in a single list
for ( int i = 0; i < (int)m_pattern.size(); i++) {
for ( int j = 0; j < (int)m_pattern[i].m_filelist.size(); j++) {
pkg_file f = m_pattern[i].m_filelist[j];
dcli_parse_filename( f.m_source, dev, dir, file, type, &version);
strcpy( f.m_arname, file);
strcat( f.m_arname, type);
// Check that this name is unic
bool new_name = false;
for (;;) {
for ( int k = 0; k < (int)m_filelist.size(); k++) {
if ( strcmp( m_filelist[k].m_arname, f.m_arname) == 0) {
strcat( f.m_arname, "x");
new_name = true;
break;
}
}
if ( !new_name)
break;
}
m_filelist.push_back( f);
}
}
for ( int i = 0; i < (int)m_filelist.size(); i++)
printf( "%s %s %s\n", m_filelist[i].m_source, m_filelist[i].m_target,
m_filelist[i].m_arname);
// Read and increment package version
sprintf( fname, "$pwrp_load/pkg_v_%s.dat", m_name);
dcli_translate_filename( fname, fname);
ifstream ifv( fname);
if ( ifv) {
ifv >> version;
ifv.close();
version++;
}
else
version = 1;
ofstream ofv( fname);
ofv << version;
ofv.close();
// Create a script that copies files to build directory
char pkg_name[80];
sprintf( pkg_name, "pwrp_pkg_%s_%04d.tgz", m_name, version);
dcli_translate_filename( pack_fname, "$pwrp_tmp/pkg_pack.sh");
ofstream of( pack_fname);
of <<
"if [ ! -e $pwrp_tmp/pkg_build ]; then" << endl <<
" mkdir $pwrp_tmp/pkg_build" << endl <<
"else" << endl <<
" rm -r $pwrp_tmp/pkg_build/*" << endl <<
"fi" << endl;
for ( int i = 0; i < (int)m_filelist.size(); i++)
of <<
"cp " << m_filelist[i].m_source << " $pwrp_tmp/pkg_build/" << m_filelist[i].m_arname << endl;
of <<
"#mv $pwrp_tmp/pkg_unpack.sh $pwrp_tmp/pkg_build" << endl <<
"cd $pwrp_tmp" << endl <<
"tar -czf " << pkg_name << " pwr_pkg.dat pkg_unpack.sh pkg_build" << endl <<
"ftp -vin " << m_name << " << EOF &>$pwrp_tmp/ftp_" << m_name << ".log" << endl <<
"user pwrp pwrp" << endl <<
"binary" << endl <<
"put " << pkg_name << endl <<
"quit" << endl <<
"EOF" << endl <<
"rsh -l pwrp " << m_name << " \\$pwr_exe/pwr_pkg.sh -i " << pkg_name << endl;
of.close();
// Create a script that unpackes the archive and moves files to the target directories
dcli_translate_filename( fname, "$pwrp_tmp/pkg_unpack.sh");
ofstream ofu( fname);
if ( !ofu)
throw wb_error_str("Unable to open file");
ofu <<
"cd /tmp" << endl <<
"echo \"-- Unpack package " << pkg_name << "\"" << endl <<
"tar -xzf /home/pwrp/" << pkg_name << endl <<
"echo \"-- Move files to target directories\"" << endl;
for ( int i = 0; i < (int)m_filelist.size(); i++)
ofu <<
"mv /tmp/pkg_build/" << m_filelist[i].m_arname << " " << m_filelist[i].m_target << endl;
ofu <<
"mv pwr_pkg.dat $pwrp_load" << endl <<
"rm -r /tmp/pkg_build" << endl;
ofu.close();
// Create a data file with description and all installed files
dcli_translate_filename( fname, "$pwrp_tmp/pwr_pkg.dat");
ofstream ofd( fname);
if ( !ofd)
throw wb_error_str("Unable to open file");
pwr_tTime time;
char time_str[32];
clock_gettime(CLOCK_REALTIME, &time);
time_AtoAscii( &time, time_eFormat_DateAndTime, time_str, sizeof(time_str));
ofd <<
"%Package: " << endl <<
pkg_name << endl <<
"%Brief:" << endl <<
"Proview package " << m_name << " Version " << version << " " << time_str << endl <<
"%Description:" << endl <<
"Proview package: " << pkg_name << endl <<
"Node: " << m_name << endl <<
"Version: " << version << endl <<
"Created: " << time_str << endl <<
"%Files:" << endl;
for ( int i = 0; i < (int)m_filelist.size(); i++) {
char timestr[32];
time_AtoAscii( &m_filelist[i].m_date, time_eFormat_DateAndTime,
timestr, sizeof(timestr));
ofd <<
m_filelist[i].m_target << " " << timestr << endl;
}
ofd <<
"%End:" << endl;
ofd.close();
// Execute the pack file
char cmd[200];
sprintf( cmd, "source %s", pack_fname);
system( cmd);
}
void pkg_pattern::fetchFiles()
{
char found_file[200];
int sts;
char dev[80];
char dir[80];
char file[80];
char type[80];
int version;
char file_target[80];
if ( strchr( m_source, ':')) {
// Source is a search path separated by colon
char source[10][120];
int num;
num = dcli_parse( m_source, ":", "", (char *)source,
sizeof(source)/sizeof(source[0]),
sizeof(source[0]), 0);
for ( int i = 0; i < num; i++) {
sts = dcli_search_file( source[i], found_file, DCLI_DIR_SEARCH_INIT);
dcli_search_file( source[i], found_file, DCLI_DIR_SEARCH_END);
if ( ODD(sts)) {
if ( hasTarget()) {
dcli_parse_filename( m_target, dev, dir, file, type, &version);
strcpy( file_target, dev);
strcpy( file_target, dir);
if ( strcmp( file, "") == 0 && strcmp( type, "") == 0)
dcli_parse_filename( found_file, dev, dir, file, type, &version);
strcat( file_target, file);
strcat( file_target, type);
}
else {
dcli_parse_filename( source[0], dev, dir, file, type, &version);
strcpy( file_target, dev);
strcat( file_target, dir);
dcli_parse_filename( found_file, dev, dir, file, type, &version);
strcat( file_target, file);
strcat( file_target, type);
}
pkg_file file( found_file, file_target);
m_filelist.push_back( file);
break;
}
}
}
else {
sts = dcli_search_file( m_source, found_file, DCLI_DIR_SEARCH_INIT);
while ( ODD(sts)) {
if ( hasTarget()) {
dcli_parse_filename( m_target, dev, dir, file, type, &version);
strcpy( file_target, dev);
strcpy( file_target, dir);
if ( strcmp( file, "") == 0 && strcmp( type, "") == 0)
dcli_parse_filename( found_file, dev, dir, file, type, &version);
strcat( file_target, file);
strcat( file_target, type);
}
else {
dcli_parse_filename( m_source, dev, dir, file, type, &version);
strcpy( file_target, dev);
strcat( file_target, dir);
dcli_parse_filename( found_file, dev, dir, file, type, &version);
strcat( file_target, file);
strcat( file_target, type);
}
pkg_file file( found_file, file_target);
m_filelist.push_back( file);
sts = dcli_search_file( m_source, found_file, DCLI_DIR_SEARCH_NEXT);
}
dcli_search_file( m_source, found_file, DCLI_DIR_SEARCH_END);
}
}
pkg_file::pkg_file( char *source, char *target)
{
struct stat info;
int sts = stat( source, &info);
if ( sts == -1)
throw wb_error_str("Source file doesn't exist");
m_date.tv_sec = info.st_ctime;
m_date.tv_nsec = 0;
strcpy( m_source, source);
strcpy( m_target, target);
}
#ifndef wb_pkg_h
#define wb_pkg_h
#include <iostream.h>
#include <vector>
#include <string>
#include "pwr.h"
#include "pwr_class.h"
#include "wb_nav_macros.h"
#include "wb_lfu.h"
class pkg_node;
class pkg_file {
friend class pkg_node;
private:
char m_source[200];
char m_target[200];
char m_arname[80];
pwr_tTime m_date;
public:
pkg_file( char *source, char *target);
char *source() { return m_source;}
char *target() { return m_target;}
pwr_tTime date() { return m_date;}
};
class pkg_pattern {
friend class pkg_node;
private:
char m_source[200];
char m_target[200];
vector<pkg_file> m_filelist;
public:
pkg_pattern( char *source, char *target) {
strcpy( m_source, source);
strcpy( m_target, target);
}
pkg_pattern( char *source) {
strcpy( m_source, source);
strcpy( m_target, "");
}
pkg_pattern( const pkg_pattern& x) : m_filelist(x.m_filelist) {
strcpy( m_source, x.m_source);
strcpy( m_target, x.m_target);
}
char *source() { return m_source;}
char *target() { return m_target;}
bool hasTarget() { return m_target[0] != 0;}
void fetchFiles();
};
class pkg_node {
private:
vector<pkg_pattern> m_pattern;
vector<pkg_file> m_filelist;
char m_name[80];
pwr_mOpSys m_opsys;
int m_bus;
lfu_eDistrSts m_dstatus;
bool m_valid;
public:
pkg_node( char *name): m_opsys(pwr_mOpSys__), m_bus(0),
m_dstatus(lfu_eDistrSts_Normal), m_valid(false) { strcpy( m_name, name);}
pkg_node( char *name, pwr_mOpSys opsys, int bus, lfu_eDistrSts dstatus) :
m_opsys(opsys), m_bus(bus), m_dstatus(dstatus),
m_valid(true) { strcpy( m_name, name); }
char *name() { return m_name;}
pwr_mOpSys opsys() { return m_opsys;}
int bus() { return m_bus;}
lfu_eDistrSts dstatus() { return m_dstatus;}
bool valid() { return m_valid;}
void setOpsys( pwr_mOpSys opsys) { m_opsys = opsys;}
void setBus( int bus) { m_bus = bus;}
void setDStatus( lfu_eDistrSts dstatus) { m_dstatus = dstatus;}
void setValid() { m_valid = true;}
void push_back( pkg_pattern& pattern) {
m_pattern.push_back( pattern);
}
void fetchFiles();
};
class wb_pkg {
private:
vector<pkg_node> m_nodelist;
bool m_allnodes;
void readConfig();
public:
wb_pkg( char *nodelist);
pkg_node& getNode( char *name);
void fetchFiles() {
for ( int i = 0; i < (int)m_nodelist.size(); i++) m_nodelist[i].fetchFiles();
}
};
#endif
...@@ -67,6 +67,7 @@ extern "C" { ...@@ -67,6 +67,7 @@ extern "C" {
#include "wb_erep.h" #include "wb_erep.h"
#include "wb_vrepwbl.h" #include "wb_vrepwbl.h"
#include "wb_vrepmem.h" #include "wb_vrepmem.h"
#include "wb_pkg.h"
#define WNAV_MENU_CREATE 0 #define WNAV_MENU_CREATE 0
#define WNAV_MENU_ADD 1 #define WNAV_MENU_ADD 1
...@@ -150,6 +151,8 @@ static int wnav_generate_func( void *client_data, ...@@ -150,6 +151,8 @@ static int wnav_generate_func( void *client_data,
void *client_flag); void *client_flag);
static int wnav_crossref_func( void *client_data, static int wnav_crossref_func( void *client_data,
void *client_flag); void *client_flag);
static int wnav_distribute_func( void *client_data,
void *client_flag);
dcli_tCmdTable wnav_command_table[] = { dcli_tCmdTable wnav_command_table[] = {
{ {
...@@ -378,6 +381,11 @@ dcli_tCmdTable wnav_command_table[] = { ...@@ -378,6 +381,11 @@ dcli_tCmdTable wnav_command_table[] = {
{ "dcli_arg1", "/NAME", "/FILE", "/STRING", "/BRIEF", { "dcli_arg1", "/NAME", "/FILE", "/STRING", "/BRIEF",
"/FUNCTION", "/CASE_SENSITIVE", ""} "/FUNCTION", "/CASE_SENSITIVE", ""}
}, },
{
"DISTRIBUTE",
&wnav_distribute_func,
{ "/NODE", ""}
},
{"",}}; {"",}};
...@@ -4141,6 +4149,33 @@ static int wnav_crossref_func( void *client_data, ...@@ -4141,6 +4149,33 @@ static int wnav_crossref_func( void *client_data,
return sts; return sts;
} }
static int wnav_distribute_func( void *client_data,
void *client_flag)
{
WNav *wnav = (WNav *)client_data;
int sts;
char *node_ptr;
char node_str[80];
if ( ODD( dcli_get_qualifier( "/NODE", node_str)))
node_ptr = node_str;
else
node_ptr = NULL;
sts = WNAV__SUCCESS;
try {
wb_pkg *pkg = new wb_pkg( node_ptr);
delete pkg;
}
catch ( wb_error &e) {
wnav->message(' ', (char *)e.what().c_str());
sts = e.sts();
}
if ( EVEN(sts))
return sts;
return 1;
}
int WNav::show_database() int WNav::show_database()
{ {
int sts; int sts;
......
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