Implementato socket server per gestire la comunicazione piGardenWeb. Iniziato feature per gestire la configurazione del crontab da script.
This commit is contained in:
@@ -14,15 +14,24 @@ JQ="/usr/local/bin/jq"
|
|||||||
# Percorso curl
|
# Percorso curl
|
||||||
CURL="/usr/bin/curl"
|
CURL="/usr/bin/curl"
|
||||||
|
|
||||||
# Percors wc
|
# Percorso wc
|
||||||
WC="/usr/bin/wc"
|
WC="/usr/bin/wc"
|
||||||
|
|
||||||
# Percorso gzip
|
# Percorso gzip
|
||||||
GZIP="/bin/gzip"
|
GZIP="/bin/gzip"
|
||||||
|
|
||||||
# Percors mv
|
# Percorso mv
|
||||||
MV="/bin/mv"
|
MV="/bin/mv"
|
||||||
|
|
||||||
|
# Percorso di tr
|
||||||
|
TR="/usr/bin/tr"
|
||||||
|
|
||||||
|
# Percorso di cut
|
||||||
|
CUT="/usr/bin/cut"
|
||||||
|
|
||||||
|
# Percorso tcpserver
|
||||||
|
TCPSERVER="/usr/bin/tcpserver"
|
||||||
|
|
||||||
# Id gpio usati per simulare il doppio deviatore con cui eseguire l'alimentazione alle elettrovalvole
|
# Id gpio usati per simulare il doppio deviatore con cui eseguire l'alimentazione alle elettrovalvole
|
||||||
SUPPLY_GPIO_1=2
|
SUPPLY_GPIO_1=2
|
||||||
SUPPLY_GPIO_2=3
|
SUPPLY_GPIO_2=3
|
||||||
@@ -82,3 +91,7 @@ NOT_IRRIGATE_IF_RAIN_ONLINE=86400
|
|||||||
# Il parametro è simile a quello precedente, il controllo però anziché essere fatto attingendo a wunderground, viene eseguito direttamente sul sensore se installato. Inserendo il valore zero non viene eseguito nessun controllo.
|
# Il parametro è simile a quello precedente, il controllo però anziché essere fatto attingendo a wunderground, viene eseguito direttamente sul sensore se installato. Inserendo il valore zero non viene eseguito nessun controllo.
|
||||||
NOT_IRRIGATE_IF_RAIN_SENSOR=86400
|
NOT_IRRIGATE_IF_RAIN_SENSOR=86400
|
||||||
|
|
||||||
|
# Indirizzo ip e porta di ascolto del socket server
|
||||||
|
TCPSERVER_IP="127.0.0.1"
|
||||||
|
TCPSERVER_PORT="8084"
|
||||||
|
|
||||||
|
|||||||
241
piGarden.sh
241
piGarden.sh
@@ -50,6 +50,12 @@ function initialize {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reset_messages {
|
||||||
|
rm -f "$LAST_INFO_FILE.$!"
|
||||||
|
rm -f "$LAST_WARNING_FILE.$!"
|
||||||
|
rm -f "$LAST_SUCCESS_FILE.$!"
|
||||||
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
# Commuta un elettrovalvola nello stato aperto
|
# Commuta un elettrovalvola nello stato aperto
|
||||||
# $1 alias elettrovalvola
|
# $1 alias elettrovalvola
|
||||||
@@ -64,6 +70,7 @@ function ev_open {
|
|||||||
let "dif = now - last_rain"
|
let "dif = now - last_rain"
|
||||||
if [ $dif -lt $NOT_IRRIGATE_IF_RAIN_ONLINE ]; then
|
if [ $dif -lt $NOT_IRRIGATE_IF_RAIN_ONLINE ]; then
|
||||||
log_write "Solenoid '$1' not open for rain (online check)"
|
log_write "Solenoid '$1' not open for rain (online check)"
|
||||||
|
message_write "warning" "Solenoid not open for rain"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -76,12 +83,14 @@ function ev_open {
|
|||||||
let "dif = now - last_rain"
|
let "dif = now - last_rain"
|
||||||
if [ $dif -lt $NOT_IRRIGATE_IF_RAIN_SENSOR ]; then
|
if [ $dif -lt $NOT_IRRIGATE_IF_RAIN_SENSOR ]; then
|
||||||
log_write "Solenoid '$1' not open for rain (sensor check)"
|
log_write "Solenoid '$1' not open for rain (sensor check)"
|
||||||
|
message_write "warning" "Solenoid not open for rain"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_write "Solenoid '$1' open"
|
log_write "Solenoid '$1' open"
|
||||||
|
message_write "success" "Solenoid open"
|
||||||
supply_positive
|
supply_positive
|
||||||
#gpio_alias2number $1
|
#gpio_alias2number $1
|
||||||
ev_alias2number $1
|
ev_alias2number $1
|
||||||
@@ -100,6 +109,7 @@ function ev_open {
|
|||||||
#
|
#
|
||||||
function ev_close {
|
function ev_close {
|
||||||
log_write "Solenoid '$1' close"
|
log_write "Solenoid '$1' close"
|
||||||
|
message_write "success" "Solenoid close"
|
||||||
supply_negative
|
supply_negative
|
||||||
#$GPIO_alias2number $1
|
#$GPIO_alias2number $1
|
||||||
ev_alias2number $1
|
ev_alias2number $1
|
||||||
@@ -144,6 +154,26 @@ function log_write {
|
|||||||
echo -e "`date`\t\t$1" >> $LOG_FILE
|
echo -e "`date`\t\t$1" >> $LOG_FILE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Scrive una tipologia di messaggio da inviare via socket server
|
||||||
|
# $1 tipo messaggio: info, warning, success
|
||||||
|
# $2 messaggio
|
||||||
|
#
|
||||||
|
function message_write {
|
||||||
|
local file_message=""
|
||||||
|
if [ "$1" = 'info' ]; then
|
||||||
|
file_message="$LAST_INFO_FILE.$!"
|
||||||
|
elif [ "$1" = "warning" ]; then
|
||||||
|
file_message="$LAST_WARNING_FILE.$!"
|
||||||
|
elif [ "$1" = "success" ]; then
|
||||||
|
file_message="$LAST_SUCCESS_FILE.$!"
|
||||||
|
else
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$2" > "$file_message"
|
||||||
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
# Imposta lo stgato di una elettrovalvola
|
# Imposta lo stgato di una elettrovalvola
|
||||||
# $1 numero dell'elettrovalvola
|
# $1 numero dell'elettrovalvola
|
||||||
@@ -177,6 +207,7 @@ function gpio_alias2number {
|
|||||||
done
|
done
|
||||||
|
|
||||||
log_write "ERROR solenoid alias not found: $1"
|
log_write "ERROR solenoid alias not found: $1"
|
||||||
|
message_write "error" "Solenoid alias not found"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,6 +226,7 @@ function ev_alias2number {
|
|||||||
done
|
done
|
||||||
|
|
||||||
log_write "ERROR solenoid alias not found: $1"
|
log_write "ERROR solenoid alias not found: $1"
|
||||||
|
message_write "error" "Solenoid alias not found"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,6 +276,7 @@ function check_rain_online {
|
|||||||
# http://www.wunderground.com/weather/api/d/docs?d=resources/phrase-glossary&MR=1
|
# http://www.wunderground.com/weather/api/d/docs?d=resources/phrase-glossary&MR=1
|
||||||
$CURL http://api.wunderground.com/api/$WUNDERGROUND_KEY/conditions/q/$WUNDERGROUND_LOCATION.json > /tmp/check_rain_online.json
|
$CURL http://api.wunderground.com/api/$WUNDERGROUND_KEY/conditions/q/$WUNDERGROUND_LOCATION.json > /tmp/check_rain_online.json
|
||||||
local weather=`cat /tmp/check_rain_online.json | $JQ -M ".current_observation.weather"`
|
local weather=`cat /tmp/check_rain_online.json | $JQ -M ".current_observation.weather"`
|
||||||
|
local current_observation=`cat /tmp/check_rain_online.json | $JQ -M ".current_observation"`
|
||||||
local local_epoch=`cat /tmp/check_rain_online.json | $JQ -M -r ".current_observation.local_epoch"`
|
local local_epoch=`cat /tmp/check_rain_online.json | $JQ -M -r ".current_observation.local_epoch"`
|
||||||
#echo $weather
|
#echo $weather
|
||||||
#weather="[Light/Heavy] Drizzle"
|
#weather="[Light/Heavy] Drizzle"
|
||||||
@@ -264,6 +297,7 @@ function check_rain_online {
|
|||||||
echo $local_epoch > "$STATUS_DIR/last_rain_online"
|
echo $local_epoch > "$STATUS_DIR/last_rain_online"
|
||||||
return $local_epoch
|
return $local_epoch
|
||||||
fi
|
fi
|
||||||
|
echo "$current_observation" > "$STATUS_DIR/last_weather_online"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,7 +359,7 @@ function close_all_for_rain {
|
|||||||
#echo "$al = $state"
|
#echo "$al = $state"
|
||||||
if [ "$state" = "1" ]; then
|
if [ "$state" = "1" ]; then
|
||||||
ev_close $al
|
ev_close $al
|
||||||
log_write "close_all_for_rain - Close solenod '$al' for rain"
|
log_write "close_all_for_rain - Close solenoid '$al' for rain"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
@@ -343,7 +377,7 @@ function close_all {
|
|||||||
#echo "$al = $state"
|
#echo "$al = $state"
|
||||||
if [[ "$state" = "1" || "$1" = "force" ]]; then
|
if [[ "$state" = "1" || "$1" = "force" ]]; then
|
||||||
ev_close $al
|
ev_close $al
|
||||||
log_write "close_all - Close solenod '$al' for rain"
|
log_write "close_all - Close solenoid '$al' for rain"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -362,6 +396,13 @@ function list_alias {
|
|||||||
|
|
||||||
function json_status {
|
function json_status {
|
||||||
local json=""
|
local json=""
|
||||||
|
local json_last_weather_online="\"\""
|
||||||
|
local json_error="\"error\":{\"code\":0,\"description\":\"\"}"
|
||||||
|
local last_rain_sensor="";
|
||||||
|
local last_rain_online="";
|
||||||
|
local last_info=""
|
||||||
|
local last_warning=""
|
||||||
|
local last_success=""
|
||||||
for i in $(seq $EV_TOTAL)
|
for i in $(seq $EV_TOTAL)
|
||||||
do
|
do
|
||||||
local a=EV"$i"_ALIAS
|
local a=EV"$i"_ALIAS
|
||||||
@@ -375,7 +416,30 @@ function json_status {
|
|||||||
done
|
done
|
||||||
json="\"zones\":{$json}"
|
json="\"zones\":{$json}"
|
||||||
|
|
||||||
json="{$json}"
|
local last_rain_sensor=`cat "$STATUS_DIR/last_rain_sensor" 2> /dev/null`
|
||||||
|
local last_rain_online=`cat "$STATUS_DIR/last_rain_online" 2> /dev/null`
|
||||||
|
|
||||||
|
local last_weather_online=`cat "$STATUS_DIR/last_weather_online" 2> /dev/null`
|
||||||
|
if [[ ! -z "$last_weather_online" ]]; then
|
||||||
|
json_last_weather_online=$last_weather_online
|
||||||
|
fi
|
||||||
|
if [ -f "$LAST_INFO_FILE.$!" ]; then
|
||||||
|
last_info=`cat "$LAST_INFO_FILE.$!"`
|
||||||
|
fi
|
||||||
|
if [ -f "$LAST_WARNING_FILE.$!" ]; then
|
||||||
|
last_warning=`cat "$LAST_WARNING_FILE.$!"`
|
||||||
|
fi
|
||||||
|
if [ -f "$LAST_SUCCESS_FILE.$!" ]; then
|
||||||
|
last_success=`cat "$LAST_SUCCESS_FILE.$!"`
|
||||||
|
fi
|
||||||
|
local json_last_weather_online="\"last_weather_online\":$json_last_weather_online"
|
||||||
|
local json_last_rain_sensor="\"last_rain_sensor\":\"$last_rain_sensor\""
|
||||||
|
local json_last_rain_online="\"last_rain_online\":\"$last_rain_online\""
|
||||||
|
local json_last_info="\"info\":\"$last_info\""
|
||||||
|
local json_last_warning="\"warning\":\"$last_warning\""
|
||||||
|
local json_last_success="\"success\":\"$last_success\""
|
||||||
|
|
||||||
|
json="{$json,$json_last_weather_online,$json_error,$json_last_info,$json_last_warning,$json_last_success,$json_last_rain_online,$json_last_rain_sensor}"
|
||||||
|
|
||||||
echo $json
|
echo $json
|
||||||
|
|
||||||
@@ -383,6 +447,29 @@ function json_status {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cron_del_check_rain_sensor {
|
||||||
|
|
||||||
|
TMP_CRON_FILE="/tmp/pigarden.user.cron"
|
||||||
|
$CRONTAB -l > /tmp/pigarden.user.cron
|
||||||
|
local START=`$GREP -n "# START cron_del_check_rain_sensor"`
|
||||||
|
local END=`$GREP -n "# END cron_del_check_rain_sensor"`
|
||||||
|
local re='^[0-9]+$'
|
||||||
|
if ! [[ $START =~ $re ]] ; then
|
||||||
|
echo "Cron start don't find" >&2
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if ! [[ $END =~ $re ]] ; then
|
||||||
|
echo "Cron end cron don't find" >&2
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [ "$START" -gt "$END" ]; then
|
||||||
|
echo "Wrong position for start and end in cron" >&2
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
$SED '$START,$ENDd' $TMP_CRON_FILE
|
||||||
|
$CRONTAB $TMP_CRON_FILE
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function show_usage {
|
function show_usage {
|
||||||
echo -e "Usage:"
|
echo -e "Usage:"
|
||||||
@@ -397,10 +484,114 @@ function show_usage {
|
|||||||
echo -e "\t$NAME_SCRIPT check_rain_sensor \tcheck rain from hardware sensor"
|
echo -e "\t$NAME_SCRIPT check_rain_sensor \tcheck rain from hardware sensor"
|
||||||
echo -e "\t$NAME_SCRIPT close_all_for_rain \tclose all solenoid if it's raining"
|
echo -e "\t$NAME_SCRIPT close_all_for_rain \tclose all solenoid if it's raining"
|
||||||
echo -e "\t$NAME_SCRIPT close_all [force]\tclose all solenoid"
|
echo -e "\t$NAME_SCRIPT close_all [force]\tclose all solenoid"
|
||||||
|
echo -e "\t$NAME_SCRIPT start_socket_server\tstart socket server"
|
||||||
|
echo -e "\t$NAME_SCRIPT stop_socket_server\tstop socket server"
|
||||||
|
echo -e "\t$NAME_SCRIPT cron_set_check_rain_sensor\tset crontab for check rein from sensor"
|
||||||
|
echo -e "\t$NAME_SCRIPT cron_del_check_rain_sensor\tremove crontab for check rein from sensor"
|
||||||
echo -e "\t$NAME_SCRIPT debug1 [parameter]|[parameter]|..]\tRun debug code 1"
|
echo -e "\t$NAME_SCRIPT debug1 [parameter]|[parameter]|..]\tRun debug code 1"
|
||||||
echo -e "\t$NAME_SCRIPT debug2 [parameter]|[parameter]|..]\tRun debug code 2"
|
echo -e "\t$NAME_SCRIPT debug2 [parameter]|[parameter]|..]\tRun debug code 2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function start_socket_server {
|
||||||
|
|
||||||
|
rm -f "$TCPSERVER_PID_FILE"
|
||||||
|
echo $TCPSERVER_PID_SCRIPT > "$TCPSERVER_PID_FILE"
|
||||||
|
$TCPSERVER -v -RHl0 $TCPSERVER_IP $TCPSERVER_PORT $0 socket_server_command
|
||||||
|
|
||||||
|
#if [ $? -eq 0 ]; then
|
||||||
|
# echo $TCPSERVER_PID_SCRIPT > "$TCPSERVER_PID_FILE"
|
||||||
|
# trap stop_socket_server EXIT
|
||||||
|
|
||||||
|
# log_write "start socket server ";
|
||||||
|
# return 0
|
||||||
|
#else
|
||||||
|
# log_write "start socket server failed";
|
||||||
|
# return 1
|
||||||
|
#fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop_socket_server {
|
||||||
|
|
||||||
|
if [ ! -f "$TCPSERVER_PID_FILE" ]; then
|
||||||
|
echo "Daemon is not running"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_write "stop socket server"
|
||||||
|
|
||||||
|
kill -9 $(list_descendants `cat "$TCPSERVER_PID_FILE"`) 2> /dev/null
|
||||||
|
kill -9 `cat "$TCPSERVER_PID_FILE"` 2> /dev/null
|
||||||
|
rm -f "$TCPSERVER_PID_FILE"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function socket_server_command {
|
||||||
|
|
||||||
|
RUN_FROM_TCPSERVER=1
|
||||||
|
|
||||||
|
local line=""
|
||||||
|
read line
|
||||||
|
line=$(echo "$line " | $TR -d '[\r\n]')
|
||||||
|
arg1=$(echo "$line " | $CUT -d ' ' -f1)
|
||||||
|
arg2=$(echo "$line " | $CUT -d ' ' -f2)
|
||||||
|
arg3=$(echo "$line " | $CUT -d ' ' -f3)
|
||||||
|
arg4=$(echo "$line " | $CUT -d ' ' -f4)
|
||||||
|
arg5=$(echo "$line " | $CUT -d ' ' -f5)
|
||||||
|
|
||||||
|
log_write "socket connection from: $TCPREMOTEIP - command: $arg1 $arg2 $arg3 $arg4 $arg5"
|
||||||
|
|
||||||
|
reset_messages &> /dev/null
|
||||||
|
|
||||||
|
case "$arg1" in
|
||||||
|
status)
|
||||||
|
json_status
|
||||||
|
;;
|
||||||
|
|
||||||
|
open)
|
||||||
|
if [ "empty$arg2" == "empty" ]; then
|
||||||
|
json_error 0 "Alias solenoid not specified"
|
||||||
|
else
|
||||||
|
ev_open $arg2 $arg3 &> /dev/null
|
||||||
|
json_status
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
close)
|
||||||
|
if [ "empty$arg2" == "empty" ]; then
|
||||||
|
json_error 0 "Alias solenoid not specified"
|
||||||
|
else
|
||||||
|
ev_close $arg2 &> /dev/null
|
||||||
|
json_status
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
json_error 0 "invalid command"
|
||||||
|
;;
|
||||||
|
|
||||||
|
esac
|
||||||
|
|
||||||
|
reset_messages &> /dev/null
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
json_error()
|
||||||
|
{
|
||||||
|
echo "{\"error\":{\"code\":$1,\"description\":\"$2\"}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
list_descendants ()
|
||||||
|
{
|
||||||
|
local children=$(ps -o pid= --ppid "$1")
|
||||||
|
|
||||||
|
for pid in $children
|
||||||
|
do
|
||||||
|
list_descendants "$pid"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$children"
|
||||||
|
}
|
||||||
|
|
||||||
function debug1 {
|
function debug1 {
|
||||||
. "$DIR_SCRIPT/debug/debug1.sh"
|
. "$DIR_SCRIPT/debug/debug1.sh"
|
||||||
}
|
}
|
||||||
@@ -412,6 +603,10 @@ function debug2 {
|
|||||||
DIR_SCRIPT=`dirname $0`
|
DIR_SCRIPT=`dirname $0`
|
||||||
NAME_SCRIPT=${0##*/}
|
NAME_SCRIPT=${0##*/}
|
||||||
CONFIG_ETC="/etc/piGarden.conf"
|
CONFIG_ETC="/etc/piGarden.conf"
|
||||||
|
TCPSERVER_PID_FILE="/tmp/piGardenTcpServer.pid"
|
||||||
|
TCPSERVER_PID_SCRIPT=$$
|
||||||
|
RUN_FROM_TCPSERVER=0
|
||||||
|
TMP_CRON_FILE="/tmp/pigarden.user.cron"
|
||||||
|
|
||||||
if [ -f $CONFIG_ETC ]; then
|
if [ -f $CONFIG_ETC ]; then
|
||||||
. $CONFIG_ETC
|
. $CONFIG_ETC
|
||||||
@@ -420,6 +615,10 @@ else
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
LAST_INFO_FILE="$STATUS_DIR/last_info"
|
||||||
|
LAST_WARNING_FILE="$STATUS_DIR/last_worning"
|
||||||
|
LAST_SUCCESS_FILE="$STATUS_DIR/last_success"
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
init)
|
init)
|
||||||
initialize
|
initialize
|
||||||
@@ -471,6 +670,42 @@ case "$1" in
|
|||||||
close_all $2
|
close_all $2
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
start_socket_server)
|
||||||
|
if [ -f "$TCPSERVER_PID_FILE" ]; then
|
||||||
|
echo "Daemon is already running, use \"$0 stop_socket_server\" to stop the service"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
nohup $0 start_socket_server_daemon > /dev/null 2>&1 &
|
||||||
|
|
||||||
|
#if [ -f "$TCPSERVER_PID_FILE" ]; then
|
||||||
|
echo "Daemon is started widh pid $!"
|
||||||
|
log_write "start socket server with pid $!"
|
||||||
|
#else
|
||||||
|
# echo "start socket server failed";
|
||||||
|
#fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
start_socket_server_daemon)
|
||||||
|
start_socket_server
|
||||||
|
;;
|
||||||
|
|
||||||
|
stop_socket_server)
|
||||||
|
stop_socket_server
|
||||||
|
;;
|
||||||
|
|
||||||
|
socket_server_command)
|
||||||
|
socket_server_command
|
||||||
|
;;
|
||||||
|
|
||||||
|
cron_set_check_rain_sensor)
|
||||||
|
cron_set_check_rain_sensor
|
||||||
|
;;
|
||||||
|
|
||||||
|
cron_del_check_rain_sensor)
|
||||||
|
cron_del_check_rain_sensor
|
||||||
|
;;
|
||||||
|
|
||||||
debug1)
|
debug1)
|
||||||
debug1 $2 $3 $4 $5
|
debug1 $2 $3 $4 $5
|
||||||
;;
|
;;
|
||||||
|
|||||||
Reference in New Issue
Block a user