2019-05-27 12:30:21 +00:00
|
|
|
#!/bin/bash
|
|
|
|
# file: modules/jsshDB.sh
|
|
|
|
# do not edit, this file will be overwritten on update
|
|
|
|
|
|
|
|
# This file is public domain in the USA and all free countries.
|
|
|
|
# Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying)
|
|
|
|
#
|
2020-06-12 19:18:32 +00:00
|
|
|
#### $$VERSION$$ v0.98-dev-0-ga8fe178
|
2019-05-27 12:30:21 +00:00
|
|
|
#
|
|
|
|
# source from commands.sh to use jsonDB functions
|
|
|
|
#
|
2019-06-04 10:00:19 +00:00
|
|
|
# jsonDB provides simple functions to read and store bash Arrays
|
|
|
|
# from to file in JSON.sh output format, its a simple key/value storage.
|
2019-05-27 12:30:21 +00:00
|
|
|
|
2020-05-15 09:53:19 +00:00
|
|
|
|
2020-05-14 17:47:37 +00:00
|
|
|
# source once magic, function named like file
|
|
|
|
eval "$(basename "${BASH_SOURCE[0]}")(){ :; }"
|
|
|
|
|
2020-05-15 09:53:19 +00:00
|
|
|
# new feature: serialize / atomic operations:
|
|
|
|
# updates will be done atomic with flock
|
|
|
|
# flock should flock should be availible on all system as its part of busybox
|
|
|
|
# tinybox
|
|
|
|
|
|
|
|
# lockfile filename.flock is persistent and will be testet with flock for active lock (file open)
|
|
|
|
export BASHBOT_LOCKNAME=".flock"
|
|
|
|
|
|
|
|
if _exists flock; then
|
|
|
|
###############
|
|
|
|
# we have flock
|
|
|
|
# use flock for atomic operations
|
|
|
|
|
|
|
|
# read content of a file in JSON.sh format into given ARRAY
|
|
|
|
# $1 ARRAY name, must be delared with "declare -A ARRAY" upfront
|
|
|
|
# $2 filename, must be relative to BASHBOT_ETC, and not contain '..'
|
|
|
|
jssh_readDB() {
|
2019-06-04 11:16:48 +00:00
|
|
|
local DB; DB="$(jssh_checkDB "$2")"
|
2020-05-14 18:33:30 +00:00
|
|
|
[ -z "${DB}" ] && return 1
|
2019-06-04 10:00:19 +00:00
|
|
|
[ ! -f "${DB}" ] && return 2
|
2020-05-20 13:18:23 +00:00
|
|
|
# shared lock, many processes can read, max wait 1s
|
2020-05-15 09:53:19 +00:00
|
|
|
{ flock -s -w 1 200; Json2Array "$1" <"${DB}"; } 200>"${DB}${BASHBOT_LOCKNAME}"
|
|
|
|
}
|
2019-05-27 12:30:21 +00:00
|
|
|
|
2020-05-15 09:53:19 +00:00
|
|
|
# write ARRAY content to a file in JSON.sh format
|
|
|
|
# Warning: old content is overwritten
|
|
|
|
# $1 ARRAY name, must be delared with "declare -A ARRAY" upfront
|
|
|
|
# $2 filename (must exist!), must be relative to BASHBOT_ETC, and not contain '..'
|
|
|
|
jssh_writeDB() {
|
2019-06-04 11:16:48 +00:00
|
|
|
local DB; DB="$(jssh_checkDB "$2")"
|
2020-05-14 18:33:30 +00:00
|
|
|
[ -z "${DB}" ] && return 1
|
2019-06-04 10:00:19 +00:00
|
|
|
[ ! -f "${DB}" ] && return 2
|
2020-05-15 09:53:19 +00:00
|
|
|
# exclusive lock, no other process can read or write, maximum wait to get lock is 10s
|
|
|
|
{ flock -e -w 10 200; Array2Json "$1" >"${DB}"; } 200>"${DB}${BASHBOT_LOCKNAME}"
|
|
|
|
}
|
2019-05-27 12:30:21 +00:00
|
|
|
|
2020-05-15 09:53:19 +00:00
|
|
|
# update/write ARRAY content in file without deleting keys not in ARRAY
|
|
|
|
# $1 ARRAY name, must be delared with "declare -A ARRAY" upfront
|
|
|
|
# $2 filename (must exist!), must be relative to BASHBOT_ETC, and not contain '..'
|
|
|
|
jssh_updateDB() {
|
|
|
|
# for atomic update we cant use read/writeDB
|
|
|
|
local DB; DB="$(jssh_checkDB "$2")"
|
|
|
|
[ -z "${DB}" ] && return 1
|
|
|
|
[ ! -f "${DB}" ] && return 2
|
2019-06-04 11:16:48 +00:00
|
|
|
declare -n ARRAY="$1"
|
2020-05-14 18:33:30 +00:00
|
|
|
[ -z "${ARRAY[*]}" ] && return 1
|
2020-05-27 07:06:22 +00:00
|
|
|
declare -A oldARR
|
2020-05-15 09:53:19 +00:00
|
|
|
|
|
|
|
# start atomic update here, exclusive max wait 10s
|
|
|
|
{ flock -e -w 10 200
|
|
|
|
Json2Array "oldARR" <"${DB}"
|
2020-05-14 18:33:30 +00:00
|
|
|
if [ -z "${oldARR[*]}" ]; then
|
2019-06-04 11:16:48 +00:00
|
|
|
# no old content
|
2020-05-15 09:53:19 +00:00
|
|
|
Array2Json "$1" >"${DB}"
|
2019-06-04 11:16:48 +00:00
|
|
|
else
|
|
|
|
# merge arrays
|
2020-05-27 07:06:22 +00:00
|
|
|
local key
|
|
|
|
for key in "${!ARRAY[@]}"
|
|
|
|
do
|
|
|
|
oldARR["${key}"]="${ARRAY["${key}"]}"
|
|
|
|
done
|
|
|
|
Array2Json "oldARR" >"${DB}"
|
2019-06-04 11:16:48 +00:00
|
|
|
fi
|
2020-05-15 09:53:19 +00:00
|
|
|
} 200>"${DB}${BASHBOT_LOCKNAME}"
|
|
|
|
}
|
2019-06-04 11:16:48 +00:00
|
|
|
|
2020-05-15 09:53:19 +00:00
|
|
|
# insert, update, apped key/value to jsshDB
|
|
|
|
# $1 key name, can onyl contain -a-zA-Z0-9,._
|
|
|
|
# $2 key value
|
|
|
|
# $3 filename (must exist!), must be relative to BASHBOT_ETC, and not contain '..'
|
2020-05-31 09:04:38 +00:00
|
|
|
alias jssh_insertDB=jssh_insertKeyDB # backward compatibility
|
|
|
|
# renamed to be more consistent
|
|
|
|
jssh_insertKeyDB() {
|
2019-06-04 10:00:19 +00:00
|
|
|
[[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3
|
|
|
|
local key="$1" value="$2"
|
2019-06-04 11:16:48 +00:00
|
|
|
local DB; DB="$(jssh_checkDB "$3")"
|
2020-05-14 18:33:30 +00:00
|
|
|
[ -z "${DB}" ] && return 1
|
2019-06-04 10:00:19 +00:00
|
|
|
[ ! -f "${DB}" ] && return 2
|
2020-05-20 13:18:23 +00:00
|
|
|
# start atomic update here, exclusive max wait 2, it's append, not overwrite
|
2020-05-15 09:53:19 +00:00
|
|
|
{ flock -e -w 2 200
|
|
|
|
# it's append, but last one counts, its a simple DB ...
|
|
|
|
printf '["%s"]\t"%s"\n' "${key//,/\",\"}" "${value//\"/\\\"}" >>"${DB}"
|
|
|
|
} 200>"${DB}${BASHBOT_LOCKNAME}"
|
2019-06-04 10:00:19 +00:00
|
|
|
|
2020-05-15 09:53:19 +00:00
|
|
|
}
|
|
|
|
|
2020-05-15 15:45:23 +00:00
|
|
|
# delete key/value from jsshDB
|
|
|
|
# $1 key name, can onyl contain -a-zA-Z0-9,._
|
|
|
|
# $2 filename (must exist!), must be relative to BASHBOT_ETC, and not contain '..'
|
|
|
|
jssh_deleteKeyDB() {
|
|
|
|
[[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3
|
2020-05-15 16:49:38 +00:00
|
|
|
local DB; DB="$(jssh_checkDB "$2")"
|
2020-05-15 15:45:23 +00:00
|
|
|
declare -A oldARR
|
|
|
|
# start atomic delete here, exclusive max wait 10s
|
|
|
|
{ flock -e -w 10 200
|
|
|
|
Json2Array "oldARR" <"${DB}"
|
2020-05-15 16:49:38 +00:00
|
|
|
unset oldARR["$1"]
|
|
|
|
Array2Json "oldARR" >"${DB}"
|
2020-05-15 15:45:23 +00:00
|
|
|
} 200>"${DB}${BASHBOT_LOCKNAME}"
|
|
|
|
}
|
|
|
|
|
2020-06-05 07:01:20 +00:00
|
|
|
# get key/value from jsshDB
|
2020-05-20 13:18:23 +00:00
|
|
|
# $1 key name, can onyl contain -a-zA-Z0-9,._
|
|
|
|
# $2 filename (must exist!), must be relative to BASHBOT_ETC, and not contain '..'
|
2020-06-05 07:01:20 +00:00
|
|
|
alias jssh_getDB=jssh_getKeyDB
|
2020-05-20 13:18:23 +00:00
|
|
|
jssh_getKeyDB() {
|
|
|
|
[[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3
|
|
|
|
local DB; DB="$(jssh_checkDB "$2")"
|
|
|
|
declare -A oldARR
|
|
|
|
# start atomic delete here, exclusive max wait 1s
|
2020-05-31 09:04:38 +00:00
|
|
|
{ flock -s -w 1 200
|
2020-05-20 13:18:23 +00:00
|
|
|
Json2Array "oldARR" <"${DB}"
|
|
|
|
} 200>"${DB}${BASHBOT_LOCKNAME}"
|
2020-06-09 20:45:34 +00:00
|
|
|
printf '%s' "${oldARR["$1"]}"
|
2020-05-20 13:18:23 +00:00
|
|
|
}
|
|
|
|
|
2020-05-15 15:45:23 +00:00
|
|
|
|
2020-05-19 16:21:56 +00:00
|
|
|
# add a value to key, used for conters
|
|
|
|
# $1 key name, can onyl contain -a-zA-Z0-9,._
|
|
|
|
# $2 filename (must exist!), must be relative to BASHBOT_ETC, and not contain '..'
|
2020-05-31 09:04:38 +00:00
|
|
|
# $3 optional count, value added to counter, add 1 if empty
|
2020-05-19 17:26:10 +00:00
|
|
|
# side effect: if $3 is not given, we add to end of file to be as fast as possible
|
2020-05-19 16:21:56 +00:00
|
|
|
jssh_countKeyDB() {
|
|
|
|
[[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3
|
2020-05-19 17:26:10 +00:00
|
|
|
local DB; DB="$(jssh_checkDB "$2")"
|
2020-05-19 16:21:56 +00:00
|
|
|
declare -A oldARR
|
2020-05-20 13:18:23 +00:00
|
|
|
# start atomic delete here, exclusive max wait 5
|
|
|
|
{ flock -e -w 5 200
|
2020-05-19 16:21:56 +00:00
|
|
|
Json2Array "oldARR" <"${DB}"
|
2020-05-19 17:26:10 +00:00
|
|
|
if [ "$3" != "" ]; then
|
|
|
|
(( oldARR["$1"]+="$3" ));
|
|
|
|
Array2Json "oldARR" >"${DB}"
|
|
|
|
else
|
|
|
|
# it's append, but last one counts, its a simple DB ...
|
|
|
|
(( oldARR["$1"]++ ));
|
|
|
|
printf '["%s"]\t"%s"\n' "${1//,/\",\"}" "${oldARR["$1"]//\"/\\\"}" >>"${DB}"
|
|
|
|
fi
|
2020-05-19 16:21:56 +00:00
|
|
|
} 200>"${DB}${BASHBOT_LOCKNAME}"
|
|
|
|
}
|
|
|
|
|
2020-06-05 20:13:18 +00:00
|
|
|
# update key/value in place to jsshDB
|
2020-06-05 07:01:20 +00:00
|
|
|
# $1 key name, can onyl contain -a-zA-Z0-9,._
|
|
|
|
# $2 key value
|
|
|
|
# $3 filename (must exist!), must be relative to BASHBOT_ETC, and not contain '..'
|
|
|
|
#no own locking, so async is the same as updatekeyDB
|
|
|
|
jssh_updateKeyDB() {
|
|
|
|
[[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3
|
2020-06-12 17:18:22 +00:00
|
|
|
declare -A updARR
|
|
|
|
# shellcheck disable=SC2034
|
|
|
|
updARR["$1"]="$2"
|
|
|
|
jssh_updateDB "updARR" "${3}" || return 3
|
2020-06-05 07:01:20 +00:00
|
|
|
}
|
|
|
|
|
2020-06-05 20:13:18 +00:00
|
|
|
# $1 filename (must exist!), must be relative to BASHBOT_ETC, and not contain '..'
|
|
|
|
jssh_clearDB() {
|
|
|
|
local DB; DB="$(jssh_checkDB "$1")"
|
|
|
|
[ -z "${DB}" ] && return 1
|
|
|
|
{ flock -e -w 10 200
|
|
|
|
printf '' >"${DB}"
|
|
|
|
} 200>"${DB}${BASHBOT_LOCKNAME}"
|
|
|
|
}
|
2020-05-19 16:21:56 +00:00
|
|
|
|
2020-05-15 09:53:19 +00:00
|
|
|
else
|
|
|
|
#########
|
|
|
|
# we have no flock, use "old" not atomic functions
|
2020-05-31 09:04:38 +00:00
|
|
|
alias jssh_readDB=ssh_readDB_async
|
|
|
|
alias jssh_writeDB=jssh_writeDB_async
|
|
|
|
alias jssh_updateDB=jssh_updateDB_async
|
|
|
|
alias jssh_insertDB=jssh_insertDB_async
|
|
|
|
alias ssh_deleteKeyDB=jssh_deleteKeyDB_async
|
2020-06-05 07:01:20 +00:00
|
|
|
alias jssh_getDB=jssh_getKeyDB_async
|
2020-05-31 09:04:38 +00:00
|
|
|
alias jssh_getKeyDB=jssh_getKeyDB_async
|
|
|
|
alias jssh_countKeyDB=jssh_countKeyDB_async
|
2020-06-05 07:01:20 +00:00
|
|
|
alias jssh_updateKeyDB=jssh_updateKeyDB_async
|
2020-06-05 20:13:18 +00:00
|
|
|
alias jssh_clearDB=jssh_clearDB_async
|
2020-05-15 09:53:19 +00:00
|
|
|
fi
|
|
|
|
|
|
|
|
##############
|
|
|
|
# no need for atomic
|
|
|
|
|
|
|
|
# print ARRAY content to stdout instead of file
|
|
|
|
# $1 ARRAY name, must be delared with "declare -A ARRAY" upfront
|
2020-06-08 10:58:36 +00:00
|
|
|
jssh_printDB_async() { jssh_printDB "$@"; }
|
2020-05-15 09:53:19 +00:00
|
|
|
jssh_printDB() {
|
|
|
|
Array2Json "$1"
|
2019-06-04 10:00:19 +00:00
|
|
|
}
|
|
|
|
|
2019-05-27 12:30:21 +00:00
|
|
|
# $1 filename (must exist!), must be relative to BASHBOT_ETC, and not contain '..'
|
2020-06-08 10:58:36 +00:00
|
|
|
jssh_newDB_async() { jssh_newDB "$@"; }
|
2019-05-27 12:30:21 +00:00
|
|
|
jssh_newDB() {
|
2019-06-04 11:16:48 +00:00
|
|
|
local DB; DB="$(jssh_checkDB "$1")"
|
2020-05-14 18:33:30 +00:00
|
|
|
[ -z "${DB}" ] && return 1
|
2020-06-09 07:15:36 +00:00
|
|
|
[ -f "${DB}" ] && return 2 # already exist
|
|
|
|
touch "${DB}"
|
2019-05-27 12:30:21 +00:00
|
|
|
}
|
2019-05-30 16:02:57 +00:00
|
|
|
|
2020-06-01 09:50:42 +00:00
|
|
|
# $1 filename, check filename, it must be relative to BASHBOT_VAR, and not contain '..'
|
2019-06-04 11:16:48 +00:00
|
|
|
# returns real path to DB file if everything is ok
|
2020-06-08 10:58:36 +00:00
|
|
|
jssh_checkDB_sync() { jssh_checkDB "$@"; }
|
2019-06-04 11:16:48 +00:00
|
|
|
jssh_checkDB(){
|
2020-06-01 09:50:42 +00:00
|
|
|
local DB
|
2020-05-14 18:33:30 +00:00
|
|
|
[ -z "$1" ] && return 1
|
2020-06-01 09:50:42 +00:00
|
|
|
[[ "$1" = *'..'* ]] && return 2
|
|
|
|
if [[ "$1" == "${BASHBOT_VAR:-.}"* ]] || [[ "$1" == "${BASHBOT_DATA:-.}"* ]]; then
|
2020-05-14 20:00:05 +00:00
|
|
|
DB="$1.jssh"
|
2020-06-01 09:50:42 +00:00
|
|
|
else
|
|
|
|
DB="${BASHBOT_VAR:-.}/$1.jssh"
|
2020-05-14 20:00:05 +00:00
|
|
|
fi
|
2020-06-09 07:15:36 +00:00
|
|
|
printf '%s' "${DB}"
|
2019-05-30 16:02:57 +00:00
|
|
|
}
|
2019-06-04 10:00:19 +00:00
|
|
|
|
2020-05-15 09:53:19 +00:00
|
|
|
|
|
|
|
######################
|
2020-06-05 07:01:20 +00:00
|
|
|
# implementations as non atomic functions
|
2020-05-15 09:53:19 +00:00
|
|
|
# can be used explictitly or as fallback if flock is not availible
|
|
|
|
jssh_readDB_async() {
|
|
|
|
local DB; DB="$(jssh_checkDB "$2")"
|
|
|
|
[ -z "${DB}" ] && return 1
|
|
|
|
[ ! -f "${DB}" ] && return 2
|
|
|
|
Json2Array "$1" <"${DB}"
|
|
|
|
}
|
|
|
|
|
|
|
|
jssh_writeDB_async() {
|
|
|
|
local DB; DB="$(jssh_checkDB "$2")"
|
|
|
|
[ -z "${DB}" ] && return 1
|
|
|
|
[ ! -f "${DB}" ] && return 2
|
|
|
|
Array2Json "$1" >"${DB}"
|
|
|
|
}
|
|
|
|
|
|
|
|
jssh_updateDB_async() {
|
|
|
|
declare -n ARRAY="$1"
|
|
|
|
[ -z "${ARRAY[*]}" ] && return 1
|
2020-05-31 09:04:38 +00:00
|
|
|
declare -A oldARR
|
2020-05-15 15:45:23 +00:00
|
|
|
jssh_readDB_async "oldARR" "$2" || return "$?"
|
2020-05-15 09:53:19 +00:00
|
|
|
if [ -z "${oldARR[*]}" ]; then
|
|
|
|
# no old content
|
2020-05-15 15:45:23 +00:00
|
|
|
jssh_writeDB_async "$1" "$2"
|
2020-05-15 09:53:19 +00:00
|
|
|
else
|
|
|
|
# merge arrays
|
2020-05-31 09:04:38 +00:00
|
|
|
local key
|
|
|
|
for key in "${!ARRAY[@]}"
|
|
|
|
do
|
|
|
|
oldARR["${key}"]="${ARRAY["${key}"]}"
|
|
|
|
done
|
|
|
|
Array2Json "oldARR" >"${DB}"
|
2020-05-15 09:53:19 +00:00
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
2020-06-08 10:58:36 +00:00
|
|
|
jssh_insertDB_async() { jssh_insertKeyDB "$@"; }
|
2020-05-31 09:04:38 +00:00
|
|
|
jssh_insertKeyDB_async() {
|
2020-05-15 09:53:19 +00:00
|
|
|
[[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3
|
|
|
|
local key="$1" value="$2"
|
|
|
|
local DB; DB="$(jssh_checkDB "$3")"
|
|
|
|
[ -z "${DB}" ] && return 1
|
|
|
|
[ ! -f "${DB}" ] && return 2
|
|
|
|
# its append, but last one counts, its a simple DB ...
|
|
|
|
printf '["%s"]\t"%s"\n' "${key//,/\",\"}" "${value//\"/\\\"}" >>"${DB}"
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-05-15 15:45:23 +00:00
|
|
|
jssh_deleteKeyDB_async() {
|
|
|
|
[[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3
|
2020-05-15 16:49:38 +00:00
|
|
|
local DB; DB="$(jssh_checkDB "$2")"
|
2020-05-15 15:45:23 +00:00
|
|
|
declare -A oldARR
|
2020-06-10 07:32:53 +00:00
|
|
|
Json2Array "oldARR" <"${DB}"
|
2020-05-15 16:49:38 +00:00
|
|
|
unset oldARR["$1"]
|
2020-06-10 07:32:53 +00:00
|
|
|
Array2Json "oldARR" >"${DB}"
|
2020-05-15 15:45:23 +00:00
|
|
|
}
|
|
|
|
|
2020-05-20 13:18:23 +00:00
|
|
|
jssh_getKeyDB_async() {
|
|
|
|
[[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3
|
|
|
|
local DB; DB="$(jssh_checkDB "$2")"
|
|
|
|
declare -A oldARR
|
|
|
|
Json2Array "oldARR" <"${DB}"
|
2020-06-09 07:15:36 +00:00
|
|
|
printf '%s' "${oldARR["$1"]}"
|
2020-05-20 13:18:23 +00:00
|
|
|
}
|
|
|
|
|
2020-05-19 16:21:56 +00:00
|
|
|
jssh_countKeyDB_async() {
|
|
|
|
[[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3
|
|
|
|
local DB COUNT="1"; DB="$(jssh_checkDB "$2")"
|
2020-05-19 17:26:10 +00:00
|
|
|
[ "$3" != "" ] && COUNT="$3"
|
2020-05-19 16:21:56 +00:00
|
|
|
declare -A oldARR
|
|
|
|
# start atomic delete here, exclusive max wait 10s
|
|
|
|
Json2Array "oldARR" <"${DB}"
|
|
|
|
(( oldARR["$1"]+=COUNT ));
|
|
|
|
Array2Json "oldARR" >"${DB}"
|
|
|
|
}
|
|
|
|
|
2020-06-05 07:01:20 +00:00
|
|
|
# updatie key/value in place to jsshDB
|
|
|
|
# $1 key name, can onyl contain -a-zA-Z0-9,._
|
|
|
|
# $2 key value
|
|
|
|
# $3 filename (must exist!), must be relative to BASHBOT_ETC, and not contain '..'
|
|
|
|
#no own locking, so async is the same as updatekeyDB
|
2020-06-05 20:13:18 +00:00
|
|
|
jssh_updateKeyDB_async() {
|
2020-06-05 07:01:20 +00:00
|
|
|
[[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3
|
2020-06-12 17:18:22 +00:00
|
|
|
declare -A updARR
|
|
|
|
# shellcheck disable=SC2034
|
|
|
|
updARR["$1"]="$2"
|
|
|
|
jssh_updateDB_async "updARR" "${3}" || return 3
|
2020-06-05 07:01:20 +00:00
|
|
|
}
|
|
|
|
|
2020-06-05 20:13:18 +00:00
|
|
|
jssh_clearDB_async() {
|
|
|
|
local DB; DB="$(jssh_checkDB "$1")"
|
|
|
|
[ -z "${DB}" ] && return 1
|
|
|
|
printf '' >"${DB}"
|
|
|
|
}
|
|
|
|
|
2020-06-05 07:01:20 +00:00
|
|
|
|