refactor: run go fmt ./... (#169)
This commit is contained in:
parent
c81393b31a
commit
163922e001
|
@ -6,9 +6,9 @@ import (
|
||||||
"github.com/alecthomas/kingpin"
|
"github.com/alecthomas/kingpin"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/ooni/probe-cli/internal/ooni"
|
|
||||||
"github.com/ooni/probe-cli/internal/cli/root"
|
"github.com/ooni/probe-cli/internal/cli/root"
|
||||||
"github.com/ooni/probe-cli/internal/config"
|
"github.com/ooni/probe-cli/internal/config"
|
||||||
|
"github.com/ooni/probe-cli/internal/ooni"
|
||||||
"github.com/ooni/probe-cli/internal/output"
|
"github.com/ooni/probe-cli/internal/output"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gopkg.in/AlecAivazis/survey.v1"
|
"gopkg.in/AlecAivazis/survey.v1"
|
||||||
|
|
|
@ -3,9 +3,9 @@ package root
|
||||||
import (
|
import (
|
||||||
"github.com/alecthomas/kingpin"
|
"github.com/alecthomas/kingpin"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/ooni/probe-cli/internal/ooni"
|
|
||||||
"github.com/ooni/probe-cli/internal/log/handlers/batch"
|
"github.com/ooni/probe-cli/internal/log/handlers/batch"
|
||||||
"github.com/ooni/probe-cli/internal/log/handlers/cli"
|
"github.com/ooni/probe-cli/internal/log/handlers/cli"
|
||||||
|
"github.com/ooni/probe-cli/internal/ooni"
|
||||||
"github.com/ooni/probe-cli/internal/utils"
|
"github.com/ooni/probe-cli/internal/utils"
|
||||||
"github.com/ooni/probe-cli/internal/version"
|
"github.com/ooni/probe-cli/internal/version"
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,11 +6,11 @@ import (
|
||||||
"github.com/alecthomas/kingpin"
|
"github.com/alecthomas/kingpin"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/ooni/probe-cli/internal/ooni"
|
|
||||||
"github.com/ooni/probe-cli/internal/cli/onboard"
|
"github.com/ooni/probe-cli/internal/cli/onboard"
|
||||||
"github.com/ooni/probe-cli/internal/cli/root"
|
"github.com/ooni/probe-cli/internal/cli/root"
|
||||||
"github.com/ooni/probe-cli/internal/database"
|
"github.com/ooni/probe-cli/internal/database"
|
||||||
"github.com/ooni/probe-cli/internal/nettests"
|
"github.com/ooni/probe-cli/internal/nettests"
|
||||||
|
"github.com/ooni/probe-cli/internal/ooni"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runNettestGroup(tg string, ctx *ooni.Context, network *database.Network) error {
|
func runNettestGroup(tg string, ctx *ooni.Context, network *database.Network) error {
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/ooni/probe-cli/internal/version"
|
"github.com/ooni/probe-cli/internal/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmd := root.Command("version", "Show version.")
|
cmd := root.Command("version", "Show version.")
|
||||||
cmd.Action(func(_ *kingpin.ParseContext) error {
|
cmd.Action(func(_ *kingpin.ParseContext) error {
|
||||||
|
|
|
@ -35,9 +35,9 @@ var websiteCategories = []string{
|
||||||
|
|
||||||
// Sharing settings
|
// Sharing settings
|
||||||
type Sharing struct {
|
type Sharing struct {
|
||||||
IncludeIP bool `json:"include_ip"`
|
IncludeIP bool `json:"include_ip"`
|
||||||
IncludeASN bool `json:"include_asn"`
|
IncludeASN bool `json:"include_asn"`
|
||||||
UploadResults bool `json:"upload_results"`
|
UploadResults bool `json:"upload_results"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Advanced settings
|
// Advanced settings
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/ooni/probe-cli/internal/ooni"
|
|
||||||
"github.com/ooni/probe-cli/internal/database"
|
"github.com/ooni/probe-cli/internal/database"
|
||||||
|
"github.com/ooni/probe-cli/internal/ooni"
|
||||||
"github.com/ooni/probe-cli/internal/output"
|
"github.com/ooni/probe-cli/internal/output"
|
||||||
engine "github.com/ooni/probe-engine"
|
engine "github.com/ooni/probe-engine"
|
||||||
"github.com/ooni/probe-engine/model"
|
"github.com/ooni/probe-engine/model"
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ooni/probe-cli/internal/ooni"
|
|
||||||
"github.com/ooni/probe-cli/internal/database"
|
"github.com/ooni/probe-cli/internal/database"
|
||||||
|
"github.com/ooni/probe-cli/internal/ooni"
|
||||||
"github.com/ooni/probe-cli/internal/utils/shutil"
|
"github.com/ooni/probe-cli/internal/utils/shutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,145 +1,140 @@
|
||||||
package shutil
|
package shutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
type SameFileError struct {
|
type SameFileError struct {
|
||||||
Src string
|
Src string
|
||||||
Dst string
|
Dst string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e SameFileError) Error() string {
|
func (e SameFileError) Error() string {
|
||||||
return fmt.Sprintf("%s and %s are the same file", e.Src, e.Dst)
|
return fmt.Sprintf("%s and %s are the same file", e.Src, e.Dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SpecialFileError struct {
|
type SpecialFileError struct {
|
||||||
File string
|
File string
|
||||||
FileInfo os.FileInfo
|
FileInfo os.FileInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e SpecialFileError) Error() string {
|
func (e SpecialFileError) Error() string {
|
||||||
return fmt.Sprintf("`%s` is a named pipe", e.File)
|
return fmt.Sprintf("`%s` is a named pipe", e.File)
|
||||||
}
|
}
|
||||||
|
|
||||||
type NotADirectoryError struct {
|
type NotADirectoryError struct {
|
||||||
Src string
|
Src string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e NotADirectoryError) Error() string {
|
func (e NotADirectoryError) Error() string {
|
||||||
return fmt.Sprintf("`%s` is not a directory", e.Src)
|
return fmt.Sprintf("`%s` is not a directory", e.Src)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type AlreadyExistsError struct {
|
type AlreadyExistsError struct {
|
||||||
Dst string
|
Dst string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e AlreadyExistsError) Error() string {
|
func (e AlreadyExistsError) Error() string {
|
||||||
return fmt.Sprintf("`%s` already exists", e.Dst)
|
return fmt.Sprintf("`%s` already exists", e.Dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func samefile(src string, dst string) bool {
|
func samefile(src string, dst string) bool {
|
||||||
srcInfo, _ := os.Stat(src)
|
srcInfo, _ := os.Stat(src)
|
||||||
dstInfo, _ := os.Stat(dst)
|
dstInfo, _ := os.Stat(dst)
|
||||||
return os.SameFile(srcInfo, dstInfo)
|
return os.SameFile(srcInfo, dstInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func specialfile(fi os.FileInfo) bool {
|
func specialfile(fi os.FileInfo) bool {
|
||||||
return (fi.Mode() & os.ModeNamedPipe) == os.ModeNamedPipe
|
return (fi.Mode() & os.ModeNamedPipe) == os.ModeNamedPipe
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringInSlice(a string, list []string) bool {
|
func stringInSlice(a string, list []string) bool {
|
||||||
for _, b := range list {
|
for _, b := range list {
|
||||||
if b == a {
|
if b == a {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsSymlink(fi os.FileInfo) bool {
|
func IsSymlink(fi os.FileInfo) bool {
|
||||||
return (fi.Mode() & os.ModeSymlink) == os.ModeSymlink
|
return (fi.Mode() & os.ModeSymlink) == os.ModeSymlink
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Copy data from src to dst
|
// Copy data from src to dst
|
||||||
//
|
//
|
||||||
// If followSymlinks is not set and src is a symbolic link, a
|
// If followSymlinks is not set and src is a symbolic link, a
|
||||||
// new symlink will be created instead of copying the file it points
|
// new symlink will be created instead of copying the file it points
|
||||||
// to.
|
// to.
|
||||||
func CopyFile(src, dst string, followSymlinks bool) (error) {
|
func CopyFile(src, dst string, followSymlinks bool) error {
|
||||||
if samefile(src, dst) {
|
if samefile(src, dst) {
|
||||||
return &SameFileError{src, dst}
|
return &SameFileError{src, dst}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure src exists and neither are special files
|
// Make sure src exists and neither are special files
|
||||||
srcStat, err := os.Lstat(src)
|
srcStat, err := os.Lstat(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if specialfile(srcStat) {
|
if specialfile(srcStat) {
|
||||||
return &SpecialFileError{src, srcStat}
|
return &SpecialFileError{src, srcStat}
|
||||||
}
|
}
|
||||||
|
|
||||||
dstStat, err := os.Stat(dst)
|
dstStat, err := os.Stat(dst)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
} else if err == nil {
|
} else if err == nil {
|
||||||
if specialfile(dstStat) {
|
if specialfile(dstStat) {
|
||||||
return &SpecialFileError{dst, dstStat}
|
return &SpecialFileError{dst, dstStat}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we don't follow symlinks and it's a symlink, just link it and be done
|
// If we don't follow symlinks and it's a symlink, just link it and be done
|
||||||
if !followSymlinks && IsSymlink(srcStat) {
|
if !followSymlinks && IsSymlink(srcStat) {
|
||||||
return os.Symlink(src, dst)
|
return os.Symlink(src, dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are a symlink, follow it
|
// If we are a symlink, follow it
|
||||||
if IsSymlink(srcStat) {
|
if IsSymlink(srcStat) {
|
||||||
src, err = os.Readlink(src)
|
src, err = os.Readlink(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
srcStat, err = os.Stat(src)
|
srcStat, err = os.Stat(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the actual copy
|
// Do the actual copy
|
||||||
fsrc, err := os.Open(src)
|
fsrc, err := os.Open(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer fsrc.Close()
|
defer fsrc.Close()
|
||||||
|
|
||||||
fdst, err := os.Create(dst)
|
fdst, err := os.Create(dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer fdst.Close()
|
defer fdst.Close()
|
||||||
|
|
||||||
size, err := io.Copy(fdst, fsrc)
|
size, err := io.Copy(fdst, fsrc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if size != srcStat.Size() {
|
if size != srcStat.Size() {
|
||||||
return fmt.Errorf("%s: %d/%d copied", src, size, srcStat.Size())
|
return fmt.Errorf("%s: %d/%d copied", src, size, srcStat.Size())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Copy mode bits from src to dst.
|
// Copy mode bits from src to dst.
|
||||||
//
|
//
|
||||||
// If followSymlinks is false, symlinks aren't followed if and only
|
// If followSymlinks is false, symlinks aren't followed if and only
|
||||||
|
@ -147,28 +142,27 @@ func CopyFile(src, dst string, followSymlinks bool) (error) {
|
||||||
// and both are symlinks this does nothing. (I don't think lchmod is
|
// and both are symlinks this does nothing. (I don't think lchmod is
|
||||||
// available in Go)
|
// available in Go)
|
||||||
func CopyMode(src, dst string, followSymlinks bool) error {
|
func CopyMode(src, dst string, followSymlinks bool) error {
|
||||||
srcStat, err := os.Lstat(src)
|
srcStat, err := os.Lstat(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dstStat, err := os.Lstat(dst)
|
dstStat, err := os.Lstat(dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// They are both symlinks and we can't change mode on symlinks.
|
// They are both symlinks and we can't change mode on symlinks.
|
||||||
if !followSymlinks && IsSymlink(srcStat) && IsSymlink(dstStat) {
|
if !followSymlinks && IsSymlink(srcStat) && IsSymlink(dstStat) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Atleast one is not a symlink, get the actual file stats
|
// Atleast one is not a symlink, get the actual file stats
|
||||||
srcStat, _ = os.Stat(src)
|
srcStat, _ = os.Stat(src)
|
||||||
err = os.Chmod(dst, srcStat.Mode())
|
err = os.Chmod(dst, srcStat.Mode())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Copy data and mode bits ("cp src dst"). Return the file's destination.
|
// Copy data and mode bits ("cp src dst"). Return the file's destination.
|
||||||
//
|
//
|
||||||
// The destination may be a directory.
|
// The destination may be a directory.
|
||||||
|
@ -178,35 +172,35 @@ func CopyMode(src, dst string, followSymlinks bool) error {
|
||||||
//
|
//
|
||||||
// If source and destination are the same file, a SameFileError will be
|
// If source and destination are the same file, a SameFileError will be
|
||||||
// rased.
|
// rased.
|
||||||
func Copy(src, dst string, followSymlinks bool) (string, error){
|
func Copy(src, dst string, followSymlinks bool) (string, error) {
|
||||||
dstInfo, err := os.Stat(dst)
|
dstInfo, err := os.Stat(dst)
|
||||||
|
|
||||||
if err == nil && dstInfo.Mode().IsDir() {
|
if err == nil && dstInfo.Mode().IsDir() {
|
||||||
dst = filepath.Join(dst, filepath.Base(src))
|
dst = filepath.Join(dst, filepath.Base(src))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return dst, err
|
return dst, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = CopyFile(src, dst, followSymlinks)
|
err = CopyFile(src, dst, followSymlinks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dst, err
|
return dst, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = CopyMode(src, dst, followSymlinks)
|
err = CopyMode(src, dst, followSymlinks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dst, err
|
return dst, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return dst, nil
|
return dst, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type CopyTreeOptions struct {
|
type CopyTreeOptions struct {
|
||||||
Symlinks bool
|
Symlinks bool
|
||||||
IgnoreDanglingSymlinks bool
|
IgnoreDanglingSymlinks bool
|
||||||
CopyFunction func (string, string, bool) (string, error)
|
CopyFunction func(string, string, bool) (string, error)
|
||||||
Ignore func (string, []os.FileInfo) []string
|
Ignore func(string, []os.FileInfo) []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursively copy a directory tree.
|
// Recursively copy a directory tree.
|
||||||
|
@ -241,86 +235,85 @@ type CopyTreeOptions struct {
|
||||||
// function that supports the same signature (like Copy2() when it
|
// function that supports the same signature (like Copy2() when it
|
||||||
// exists) can be used.
|
// exists) can be used.
|
||||||
func CopyTree(src, dst string, options *CopyTreeOptions) error {
|
func CopyTree(src, dst string, options *CopyTreeOptions) error {
|
||||||
if options == nil {
|
if options == nil {
|
||||||
options = &CopyTreeOptions{Symlinks:false,
|
options = &CopyTreeOptions{Symlinks: false,
|
||||||
Ignore:nil,
|
Ignore: nil,
|
||||||
CopyFunction:Copy,
|
CopyFunction: Copy,
|
||||||
IgnoreDanglingSymlinks:false}
|
IgnoreDanglingSymlinks: false}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
srcFileInfo, err := os.Stat(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
srcFileInfo, err := os.Stat(src)
|
if !srcFileInfo.IsDir() {
|
||||||
if err != nil {
|
return &NotADirectoryError{src}
|
||||||
return err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !srcFileInfo.IsDir() {
|
_, err = os.Open(dst)
|
||||||
return &NotADirectoryError{src}
|
if !os.IsNotExist(err) {
|
||||||
}
|
return &AlreadyExistsError{dst}
|
||||||
|
}
|
||||||
|
|
||||||
_, err = os.Open(dst)
|
entries, err := ioutil.ReadDir(src)
|
||||||
if !os.IsNotExist(err) {
|
if err != nil {
|
||||||
return &AlreadyExistsError{dst}
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
entries, err := ioutil.ReadDir(src)
|
err = os.MkdirAll(dst, srcFileInfo.Mode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.MkdirAll(dst, srcFileInfo.Mode())
|
ignoredNames := []string{}
|
||||||
if err != nil {
|
if options.Ignore != nil {
|
||||||
return err
|
ignoredNames = options.Ignore(src, entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
ignoredNames := []string{}
|
for _, entry := range entries {
|
||||||
if options.Ignore != nil {
|
if stringInSlice(entry.Name(), ignoredNames) {
|
||||||
ignoredNames = options.Ignore(src, entries)
|
continue
|
||||||
}
|
}
|
||||||
|
srcPath := filepath.Join(src, entry.Name())
|
||||||
|
dstPath := filepath.Join(dst, entry.Name())
|
||||||
|
|
||||||
for _, entry := range entries {
|
entryFileInfo, err := os.Lstat(srcPath)
|
||||||
if stringInSlice(entry.Name(), ignoredNames) {
|
if err != nil {
|
||||||
continue
|
return err
|
||||||
}
|
}
|
||||||
srcPath := filepath.Join(src, entry.Name())
|
|
||||||
dstPath := filepath.Join(dst, entry.Name())
|
|
||||||
|
|
||||||
entryFileInfo, err := os.Lstat(srcPath)
|
// Deal with symlinks
|
||||||
if err != nil {
|
if IsSymlink(entryFileInfo) {
|
||||||
return err
|
linkTo, err := os.Readlink(srcPath)
|
||||||
}
|
if err != nil {
|
||||||
|
return err
|
||||||
// Deal with symlinks
|
}
|
||||||
if IsSymlink(entryFileInfo) {
|
if options.Symlinks {
|
||||||
linkTo, err := os.Readlink(srcPath)
|
os.Symlink(linkTo, dstPath)
|
||||||
if err != nil {
|
//CopyStat(srcPath, dstPath, false)
|
||||||
return err
|
} else {
|
||||||
}
|
// ignore dangling symlink if flag is on
|
||||||
if options.Symlinks {
|
_, err = os.Stat(linkTo)
|
||||||
os.Symlink(linkTo, dstPath)
|
if os.IsNotExist(err) && options.IgnoreDanglingSymlinks {
|
||||||
//CopyStat(srcPath, dstPath, false)
|
continue
|
||||||
} else {
|
}
|
||||||
// ignore dangling symlink if flag is on
|
_, err = options.CopyFunction(srcPath, dstPath, false)
|
||||||
_, err = os.Stat(linkTo)
|
if err != nil {
|
||||||
if os.IsNotExist(err) && options.IgnoreDanglingSymlinks {
|
return err
|
||||||
continue
|
}
|
||||||
}
|
}
|
||||||
_, err = options.CopyFunction(srcPath, dstPath, false)
|
} else if entryFileInfo.IsDir() {
|
||||||
if err != nil {
|
err = CopyTree(srcPath, dstPath, options)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
}
|
}
|
||||||
} else if entryFileInfo.IsDir() {
|
} else {
|
||||||
err = CopyTree(srcPath, dstPath, options)
|
_, err = options.CopyFunction(srcPath, dstPath, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
_, err = options.CopyFunction(srcPath, dstPath, false)
|
}
|
||||||
if err != nil {
|
return nil
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user