migrate
This commit is contained in:
parent
c71519707f
commit
6a233fb76c
25
LICENSE
Normal file
25
LICENSE
Normal file
@ -0,0 +1,25 @@
|
||||
X11 License (X11)
|
||||
|
||||
Copyright (c) 2018 Diego Fernando Carrión
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the name(s) of the above copyright holders
|
||||
shall not be used in advertising or otherwise to promote the sale, use or other
|
||||
dealings in this Software without prior written authorization.
|
70
assert/assert.go
Normal file
70
assert/assert.go
Normal file
@ -0,0 +1,70 @@
|
||||
package assert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
eval "github.com/novalagung/go-eek"
|
||||
|
||||
"gitlab.com/CRThaze/cworthy/exits"
|
||||
)
|
||||
|
||||
type EvalVar eval.Var
|
||||
type EvalVal eval.ExecVar
|
||||
|
||||
const (
|
||||
stdAssertFailureFmt = "%s%s%s:%d %s%sAssertion `%s` failed.\n"
|
||||
)
|
||||
|
||||
var (
|
||||
AssertFailureFmt = stdAssertFailureFmt
|
||||
AssertionStream = os.Stderr
|
||||
)
|
||||
|
||||
func assertFailBase(frmt, assertion, file string, line int, function string) {
|
||||
fmt.Fprintf(AssertionStream, frmt, file, line, function, assertion)
|
||||
exits.Abort()
|
||||
}
|
||||
|
||||
func assertFail(frmt, expr string) {
|
||||
if pc, file, line, ok := runtime.Caller(2); ok {
|
||||
frames := runtime.CallersFrames([]uintptr{pc})
|
||||
firstFrame, _ := frames.Next()
|
||||
assertFailBase(frmt, expr, file, line, firstFrame.Function)
|
||||
}
|
||||
}
|
||||
|
||||
func Assert(exprResult bool) {
|
||||
if exprResult {
|
||||
return
|
||||
}
|
||||
assertFail(AssertFailureFmt, "")
|
||||
}
|
||||
|
||||
func AssertEval(expr string, vals EvalVal, vars ...EvalVar) {
|
||||
defer func() {
|
||||
if paniced := recover(); paniced != nil {
|
||||
assertFail("Assertion paniced", expr)
|
||||
}
|
||||
}()
|
||||
|
||||
obj := eval.New("Assertion Eval", expr)
|
||||
for _, v := range vars {
|
||||
obj.DefineVariable(v)
|
||||
}
|
||||
obj.PrepareEvaluation(expr)
|
||||
if err := obj.Build(); err != nil {
|
||||
assertFail("Assertion cannot build", expr)
|
||||
}
|
||||
resultItf, err := obj.Evaluate(vals)
|
||||
if err != nil {
|
||||
assertFail("Assertion could not be evaluated", expr)
|
||||
}
|
||||
if result, ok := resultItf.(bool); !ok {
|
||||
panic("Invalid assertion")
|
||||
} else if result {
|
||||
return
|
||||
}
|
||||
assertFail(AssertFailureFmt, expr)
|
||||
}
|
384
errno/err.go
Normal file
384
errno/err.go
Normal file
@ -0,0 +1,384 @@
|
||||
package errgo
|
||||
|
||||
type ErrorT uint16
|
||||
|
||||
const (
|
||||
NULL ErrorT = iota
|
||||
/****************
|
||||
* Posix Errors *
|
||||
****************/
|
||||
// Argument list too long.
|
||||
E2BIG
|
||||
// Permission denied.
|
||||
EACCES
|
||||
// Address in use.
|
||||
EADDRINUSE
|
||||
// Address not available.
|
||||
EADDRNOTAVAIL
|
||||
// Address family not supported.
|
||||
EAFNOSUPPORT
|
||||
// Resource unavailable, try again.
|
||||
EAGAIN
|
||||
// Connection already in progress.
|
||||
EALREADY
|
||||
// Bad file descriptor.
|
||||
EBADF
|
||||
// Bad message.
|
||||
EBADMSG
|
||||
// Device or resource busy.
|
||||
EBUSY
|
||||
// Operation canceled.
|
||||
ECANCELED
|
||||
// No child processes.
|
||||
ECHILD
|
||||
// Connection aborted.
|
||||
ECONNABORTED
|
||||
// Connection refused.
|
||||
ECONNREFUSED
|
||||
// Connection reset.
|
||||
ECONNRESET
|
||||
// Resource deadlock would occur.
|
||||
EDEADLK
|
||||
// Destination address required.
|
||||
EDESTADDRREQ
|
||||
// Mathematics argument out of domain of function.
|
||||
EDOM
|
||||
// Reserved.
|
||||
EDQUOT
|
||||
// File exists.
|
||||
EEXIST
|
||||
// Bad address.
|
||||
EFAULT
|
||||
// File too large.
|
||||
EFBIG
|
||||
// Host is unreachable.
|
||||
EHOSTUNREACH
|
||||
// Identifier removed.
|
||||
EIDRM
|
||||
// Illegal byte sequence.
|
||||
EILSEQ
|
||||
// Operation in progress.
|
||||
EINPROGRESS
|
||||
// Interrupted function.
|
||||
EINTR
|
||||
// Invalid argument.
|
||||
EINVAL
|
||||
// I/O error.
|
||||
EIO
|
||||
// Socket is connected.
|
||||
EISCONN
|
||||
// Is a directory.
|
||||
EISDIR
|
||||
// Too many levels of symbolic links.
|
||||
ELOOP
|
||||
// File descriptor value too large.
|
||||
EMFILE
|
||||
// Too many links.
|
||||
EMLINK
|
||||
// Message too large.
|
||||
EMSGSIZE
|
||||
// Reserved.
|
||||
EMULTIHOP
|
||||
// Filename too long.
|
||||
ENAMETOOLONG
|
||||
// Network is down.
|
||||
ENETDOWN
|
||||
// Connection aborted by network.
|
||||
ENETRESET
|
||||
// Network unreachable.
|
||||
ENETUNREACH
|
||||
// Too many files open in system.
|
||||
ENFILE
|
||||
// No buffer space available.
|
||||
ENOBUFS
|
||||
// No message is available on the STREAM head read queue.
|
||||
ENODATA
|
||||
// No such device.
|
||||
ENODEV
|
||||
// No such file or directory.
|
||||
ENOENT
|
||||
// Executable file format error.
|
||||
ENOEXEC
|
||||
// No locks available.
|
||||
ENOLCK
|
||||
// Reserved.
|
||||
ENOLINK
|
||||
// Not enough space.
|
||||
ENOMEM
|
||||
// No message of the desired type.
|
||||
ENOMSG
|
||||
// Protocol not available.
|
||||
ENOPROTOOPT
|
||||
// No space left on device.
|
||||
ENOSPC
|
||||
// No STREAM resources.
|
||||
ENOSR
|
||||
// Not a STREAM.
|
||||
ENOSTR
|
||||
// Functionality not supported.
|
||||
ENOSYS
|
||||
// The socket is not connected.
|
||||
ENOTCONN
|
||||
// Not a directory or a symbolic link to a directory.
|
||||
ENOTDIR
|
||||
// Directory not empty.
|
||||
ENOTEMPTY
|
||||
// State not recoverable.
|
||||
ENOTRECOVERABLE
|
||||
// Not a socket.
|
||||
ENOTSOCK
|
||||
// Not supported.
|
||||
ENOTSUP
|
||||
// Inappropriate I/O control operation.
|
||||
ENOTTY
|
||||
// No such device or address.
|
||||
ENXIO
|
||||
// Operation not supported on socket.
|
||||
EOPNOTSUPP
|
||||
// Value too large to be stored in data type.
|
||||
EOVERFLOW
|
||||
// Previous owner died.
|
||||
EOWNERDEAD
|
||||
// Operation not permitted.
|
||||
EPERM
|
||||
// Broken pipe.
|
||||
EPIPE
|
||||
// Protocol error.
|
||||
EPROTO
|
||||
// Protocol not supported.
|
||||
EPROTONOSUPPORT
|
||||
// Protocol wrong type for socket.
|
||||
EPROTOTYPE
|
||||
// Result too large.
|
||||
ERANGE
|
||||
// Read-only file system.
|
||||
EROFS
|
||||
// Invalid seek.
|
||||
ESPIPE
|
||||
// No such process.
|
||||
ESRCH
|
||||
// Reserved.
|
||||
ESTALE
|
||||
// Stream ioctl() timeout.
|
||||
ETIME
|
||||
// Connection timed out.
|
||||
ETIMEDOUT
|
||||
// Text file busy.
|
||||
ETXTBSY
|
||||
// Operation would block.
|
||||
EWOULDBLOCK
|
||||
// Cross-device link.
|
||||
EXDEV
|
||||
|
||||
/*************************
|
||||
* ErrGo Specific Errors *
|
||||
*************************/
|
||||
// Generic error.
|
||||
EOTHER ErrorT = 16384 + iota
|
||||
// ID is already in use.
|
||||
EIDINUSE
|
||||
// Already set.
|
||||
EALREADYSET
|
||||
// Not found.
|
||||
ENOTFOUND
|
||||
)
|
||||
|
||||
var errorTable = map[ErrorT]string{
|
||||
/****************
|
||||
* Posix Errors *
|
||||
****************/
|
||||
E2BIG: "Argument list too long",
|
||||
EACCES: "Permission denied",
|
||||
EADDRINUSE: "Address in use",
|
||||
EADDRNOTAVAIL: "Address not available",
|
||||
EAFNOSUPPORT: "Address family not supported",
|
||||
EAGAIN: "Resource unavailable, try again",
|
||||
EALREADY: "Connection already in progress",
|
||||
EBADF: "Bad file descriptor",
|
||||
EBADMSG: "Bad message",
|
||||
EBUSY: "Device or resource busy",
|
||||
ECANCELED: "Operation canceled",
|
||||
ECHILD: "No child processes",
|
||||
ECONNABORTED: "Connection aborted",
|
||||
ECONNREFUSED: "Connection refused",
|
||||
ECONNRESET: "Connection reset",
|
||||
EDEADLK: "Resource deadlock would occur",
|
||||
EDESTADDRREQ: "Destination address required",
|
||||
EDOM: "Mathematics argument out of domain of function",
|
||||
EDQUOT: "Reserved",
|
||||
EEXIST: "File exists",
|
||||
EFAULT: "Bad address",
|
||||
EFBIG: "File too large",
|
||||
EHOSTUNREACH: "Host is unreachable",
|
||||
EIDRM: "Identifier removed",
|
||||
EILSEQ: "Illegal byte sequence",
|
||||
EINPROGRESS: "Operation in progress",
|
||||
EINTR: "Interrupted function",
|
||||
EINVAL: "Invalid argument",
|
||||
EIO: "I/O error",
|
||||
EISCONN: "Socket is connected",
|
||||
EISDIR: "Is a directory",
|
||||
ELOOP: "Too many levels of symbolic links",
|
||||
EMFILE: "File descriptor value too large",
|
||||
EMLINK: "Too many links",
|
||||
EMSGSIZE: "Message too large",
|
||||
EMULTIHOP: "Reserved",
|
||||
ENAMETOOLONG: "Filename too long",
|
||||
ENETDOWN: "Network is down",
|
||||
ENETRESET: "Connection aborted by network",
|
||||
ENETUNREACH: "Network unreachable",
|
||||
ENFILE: "Too many files open in system",
|
||||
ENOBUFS: "No buffer space available",
|
||||
ENODATA: "No message is available on the STREAM head read queue",
|
||||
ENODEV: "No such device",
|
||||
ENOENT: "No such file or directory",
|
||||
ENOEXEC: "Executable file format error",
|
||||
ENOLCK: "No locks available",
|
||||
ENOLINK: "Reserved",
|
||||
ENOMEM: "Not enough space",
|
||||
ENOMSG: "No message of the desired type",
|
||||
ENOPROTOOPT: "Protocol not available",
|
||||
ENOSPC: "No space left on device",
|
||||
ENOSR: "No STREAM resources",
|
||||
ENOSTR: "Not a STREAM",
|
||||
ENOSYS: "Functionality not supported",
|
||||
ENOTCONN: "The socket is not connected",
|
||||
ENOTDIR: "Not a directory or a symbolic link to a directory",
|
||||
ENOTEMPTY: "Directory not empty",
|
||||
ENOTRECOVERABLE: "State not recoverable",
|
||||
ENOTSOCK: "Not a socket",
|
||||
ENOTSUP: "Not supported",
|
||||
ENOTTY: "Inappropriate I/O control operation",
|
||||
ENXIO: "No such device or address",
|
||||
EOPNOTSUPP: "Operation not supported on socket",
|
||||
EOVERFLOW: "Value too large to be stored in data type",
|
||||
EOWNERDEAD: "Previous owner died",
|
||||
EPERM: "Operation not permitted",
|
||||
EPIPE: "Broken pipe",
|
||||
EPROTO: "Protocol error",
|
||||
EPROTONOSUPPORT: "Protocol not supported",
|
||||
EPROTOTYPE: "Protocol wrong type for socket",
|
||||
ERANGE: "Result too large",
|
||||
EROFS: "Read-only file system",
|
||||
ESPIPE: "Invalid seek",
|
||||
ESRCH: "No such process",
|
||||
ESTALE: "Reserved",
|
||||
ETIME: "Stream ioctl() timeout",
|
||||
ETIMEDOUT: "Connection timed out",
|
||||
ETXTBSY: "Text file busy",
|
||||
EWOULDBLOCK: "Operation would block",
|
||||
EXDEV: "Cross-device link",
|
||||
/*************************
|
||||
* ErrGo Specific Errors *
|
||||
*************************/
|
||||
EOTHER: "Generic error",
|
||||
EIDINUSE: "ID is already in use",
|
||||
EALREADYSET: "Already set",
|
||||
ENOTFOUND: "Not found",
|
||||
}
|
||||
|
||||
const userErrorIDSpaceStart = uint16(1) << 15
|
||||
|
||||
var highestUserErrorID uint16 = userErrorIDSpaceStart
|
||||
|
||||
// Error is required to satisfy the Go error interface.
|
||||
func (err ErrorT) Error() string {
|
||||
return errorTable[err]
|
||||
}
|
||||
|
||||
// RegisterMsg allows you to set the error message on manually created ErrorT's.
|
||||
func (err ErrorT) RegisterMsg(msg string) error {
|
||||
if _, nok := errorTable[err]; nok {
|
||||
return EALREADYSET
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MustRegisterMsg is identical to RegisterMsg but will panic on errors.
|
||||
func (err ErrorT) MustRegisterMsg(msg string) {
|
||||
if e := err.RegisterMsg(msg); e != nil {
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
// ID is shorthand for uint16(err), when err is an ErrorT.
|
||||
func (err ErrorT) ID() uint16 {
|
||||
return uint16(err)
|
||||
}
|
||||
|
||||
// NewErrorT is a factory for ErrorT.
|
||||
// In the event that an invalid id is provided it will return an empty ErrorT in the first value
|
||||
// and one of the following in the second:
|
||||
// EINVAL When the id provided is not >32767
|
||||
// EIDINUSE When the ID is already in use.
|
||||
func NewErrorT(id uint16, msg string) (ErrorT, error) {
|
||||
if id < userErrorIDSpaceStart {
|
||||
return ErrorT(0), EINVAL
|
||||
}
|
||||
err := ErrorT(id)
|
||||
if _, nok := errorTable[err]; nok {
|
||||
return ErrorT(0), EIDINUSE
|
||||
}
|
||||
err.MustRegisterMsg(msg)
|
||||
return err, nil
|
||||
}
|
||||
|
||||
// MustNewErrorT is identical to NewErrorT but will panic on errors.
|
||||
func MustNewErrorT(id uint16, msg string) ErrorT {
|
||||
err, e := NewErrorT(id, msg)
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// NewNextErrorT is a factory for ErrorT which attempt to auto assign an ID >32767 and <65536.
|
||||
// In the event that 32768 errors have already been created in this way, it will return an empty
|
||||
// ErrorT in the first value and EOVERFLOW in the second.
|
||||
func NewNextErrorT(msg string) (ErrorT, error) {
|
||||
if highestUserErrorID == ^uint16(0) {
|
||||
return ErrorT(0), EOVERFLOW
|
||||
}
|
||||
id := highestUserErrorID
|
||||
highestUserErrorID++
|
||||
err := ErrorT(id)
|
||||
err.MustRegisterMsg(msg)
|
||||
return err, nil
|
||||
}
|
||||
|
||||
// MustNewNextErrorT is identical to NewNextErrorT but will panic on errors.
|
||||
func MustNewNextErrorT(msg string) ErrorT {
|
||||
err, e := NewNextErrorT(msg)
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Lookup takes an id and attempts to find an ErrorT which matches it.
|
||||
func Lookup(id uint16) (err ErrorT, msg string, e error) {
|
||||
for err, msg := range errorTable {
|
||||
if uint16(err) == id {
|
||||
return err, msg, nil
|
||||
}
|
||||
}
|
||||
return ErrorT(0), "", ENOTFOUND
|
||||
}
|
||||
|
||||
// Assert will perform a type assertion on a given error to ErrorT.
|
||||
func Assert(e error) (ErrorT, error) {
|
||||
err, ok := e.(ErrorT)
|
||||
if !ok {
|
||||
return ErrorT(0), EINVAL
|
||||
}
|
||||
return err, nil
|
||||
}
|
||||
|
||||
// MustAssert is identical to Assert but will panic on errors.
|
||||
func MustAssert(e error) ErrorT {
|
||||
err, e := Assert(e)
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return err
|
||||
}
|
14
exits/abort.go
Normal file
14
exits/abort.go
Normal file
@ -0,0 +1,14 @@
|
||||
package exits
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func Abort() {
|
||||
proc, err := os.FindProcess(os.Getpid())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
proc.Signal(syscall.SIGABRT)
|
||||
}
|
30
exits/exits.go
Normal file
30
exits/exits.go
Normal file
@ -0,0 +1,30 @@
|
||||
package exits
|
||||
|
||||
import (
|
||||
"gitlab.com/CRThaze/cworthy/errno"
|
||||
)
|
||||
|
||||
type ErrorT errno.ErrorT
|
||||
|
||||
const (
|
||||
EX_OK = 0 // successful termination
|
||||
EX__BASE = 64 // base value for error messages
|
||||
EX_USAGE = 64 // command line usage error
|
||||
EX_DATAERR = 65 // data format error
|
||||
EX_NOINPUT = 66 // cannot open input
|
||||
EX_NOUSER = 67 // addressee unknown
|
||||
EX_NOHOST = 68 // host name unknown
|
||||
EX_UNAVAILABLE = 69 // service unavailable
|
||||
EX_SOFTWARE = 70 // internal software error
|
||||
EX_OSERR = 71 // system error (e.g., can't fork)
|
||||
EX_OSFILE = 72 // critical OS file missing
|
||||
EX_CANTCREAT = 73 // can't create (user) output file
|
||||
EX_IOERR = 74 // input/output error
|
||||
EX_TEMPFAIL = 75 // temp failure; user is invited to retry
|
||||
EX_PROTOCOL = 76 // remote error in protocol
|
||||
EX_NOPERM = 77 // permission denied
|
||||
EX_CONFIG = 78 // configuration error
|
||||
EX__MAX = 78 // maximum listed value
|
||||
|
||||
EX_ABORT = 134
|
||||
)
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module gitlab.com/CRThaze/cworthy
|
||||
|
||||
go 1.17
|
||||
|
||||
require github.com/novalagung/go-eek v1.0.1
|
15
go.sum
Normal file
15
go.sum
Normal file
@ -0,0 +1,15 @@
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/novalagung/go-eek v1.0.1 h1:0KCCoccqz8Jp5ru6QynOp5F4b01rDrc7iHSSry08jG4=
|
||||
github.com/novalagung/go-eek v1.0.1/go.mod h1:Q9MQtLRP21rO5/7UmIJUr5URe1ysI+K/wM3zYJCxPxo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
Loading…
Reference in New Issue
Block a user