|
@@ -0,0 +1,481 @@ |
|
|
|
|
|
package handle |
|
|
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
|
"encoding/json" |
|
|
|
|
|
"fmt" |
|
|
|
|
|
"fts/config" |
|
|
|
|
|
"github.com/gorilla/websocket" |
|
|
|
|
|
shell "github.com/ipfs/go-ipfs-api" |
|
|
|
|
|
_ "github.com/ipfs/go-ipfs-api" |
|
|
|
|
|
"io" |
|
|
|
|
|
"log" |
|
|
|
|
|
"os" |
|
|
|
|
|
"os/exec" |
|
|
|
|
|
"path/filepath" |
|
|
|
|
|
"strconv" |
|
|
|
|
|
"strings" |
|
|
|
|
|
"time" |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
var gobalLoginUserId string |
|
|
|
|
|
|
|
|
|
|
|
//key:filepath,value:hash |
|
|
|
|
|
var gobalFileMap = make(map[string] string) |
|
|
|
|
|
var gobalFileUpdateTimeMap = make(map[string] string) |
|
|
|
|
|
|
|
|
|
|
|
//var gobalFileChangeMap = make(map[string] string) |
|
|
|
|
|
var getLocalFileListDir string |
|
|
|
|
|
var gobalSubscriptionFileChangeSwitch int =0 //订阅文件变更开关 |
|
|
|
|
|
|
|
|
|
|
|
var ipfsPath=os.Getenv("IPFS-PATH")+"ipfs.exe" |
|
|
|
|
|
/** |
|
|
|
|
|
文件上传下载进度 |
|
|
|
|
|
*/ |
|
|
|
|
|
type processStruct struct { |
|
|
|
|
|
Size string `json:"size"` |
|
|
|
|
|
CurrentSize string `json:"currentSize"` |
|
|
|
|
|
Unit string `json:"unit"` |
|
|
|
|
|
Process float64 `json:"process"` |
|
|
|
|
|
Hash string `json:"hash"` |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func main() { |
|
|
|
|
|
//config.InitConfig() |
|
|
|
|
|
//InitLocalWorkSpace("320872793405132801","test1") |
|
|
|
|
|
// |
|
|
|
|
|
//DownCommand("QmTp2hEo8eXRp6wg7jXv1BLCMh5a4F3B7buAUZNZUu772j","testOne","hello.txt","a/b/") |
|
|
|
|
|
// |
|
|
|
|
|
//UploadCommand("C:\\Users\\yuan_rh\\Downloads\\QmRzN7uW6HCVAkGMXNWv3rC9dqPJijvEgvtW6DKsQQE8Js","QmRzN7uW6HCVAkGMXNWv3rC9dqPJijvEgvtW6DKsQQE8Js","testOne","a/b/") |
|
|
|
|
|
// |
|
|
|
|
|
//GetLocalFileList("testOne") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
初始化本地工作目录 |
|
|
|
|
|
@param userId 用户ID |
|
|
|
|
|
@param projectName 项目名称 |
|
|
|
|
|
*/ |
|
|
|
|
|
func InitLocalWorkSpace(userId,projectName string) (error){ |
|
|
|
|
|
|
|
|
|
|
|
//初始化当前登陆用户 |
|
|
|
|
|
gobalLoginUserId = userId |
|
|
|
|
|
|
|
|
|
|
|
// 检查本地目录是否存在 |
|
|
|
|
|
var projectPath = config.LocalWorkSpaceDir +"\\"+userId+"\\"+projectName |
|
|
|
|
|
_,err := os.Stat(projectPath) |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
//创建文件目录 |
|
|
|
|
|
os.MkdirAll(projectPath, os.ModePerm) |
|
|
|
|
|
/*os.MkdirAll(projectPath+"\\我的文件", os.ModePerm) |
|
|
|
|
|
os.MkdirAll(projectPath+"\\工作文件", os.ModePerm) |
|
|
|
|
|
os.MkdirAll(projectPath+"\\协作文件", os.ModePerm) |
|
|
|
|
|
os.MkdirAll(projectPath+"\\公共文件", os.ModePerm)*/ |
|
|
|
|
|
} |
|
|
|
|
|
return nil |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
下载指令 |
|
|
|
|
|
@param hash ipfs哈希值 |
|
|
|
|
|
@param projectName 项目名称 |
|
|
|
|
|
@para fileName 文件名称 |
|
|
|
|
|
@param dir 云文件目录 |
|
|
|
|
|
*/ |
|
|
|
|
|
func DownCommand(conn *websocket.Conn, hash, projectName, fileName, dir string) error{ |
|
|
|
|
|
|
|
|
|
|
|
absoluteDir := config.LocalWorkSpaceDir+"\\"+gobalLoginUserId+"\\"+projectName+"\\"+dir |
|
|
|
|
|
//检查目录 |
|
|
|
|
|
_,err := os.Stat(absoluteDir) |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
//创建文件目录 |
|
|
|
|
|
err = os.MkdirAll(absoluteDir, os.ModePerm) |
|
|
|
|
|
if err!=nil{ |
|
|
|
|
|
return err |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cmd := exec.Command(ipfsPath,"get", hash,"-o",fmt.Sprint(absoluteDir+"\\"+fileName)) |
|
|
|
|
|
progress := make(chan string,10000) |
|
|
|
|
|
var stdout, stderr []byte |
|
|
|
|
|
var errStdout, errStderr error |
|
|
|
|
|
stdoutIn, _ := cmd.StdoutPipe() |
|
|
|
|
|
stderrIn, _ := cmd.StderrPipe() |
|
|
|
|
|
cmd.Start() |
|
|
|
|
|
go func() { |
|
|
|
|
|
stdout, errStdout = copyAndCapture(os.Stdout, stdoutIn, progress) |
|
|
|
|
|
}() |
|
|
|
|
|
go func() { |
|
|
|
|
|
stderr, errStderr = copyAndCapture(os.Stderr, stderrIn, progress) |
|
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
|
|
go func(){ |
|
|
|
|
|
seconds := time.Now().Unix() |
|
|
|
|
|
for content := range progress { // 通道关闭后会退出for range循环 |
|
|
|
|
|
current :=time.Now().Unix() |
|
|
|
|
|
if current-seconds>1{ |
|
|
|
|
|
projson,err := contentToJSONByte(content) |
|
|
|
|
|
if projson==nil && err==nil{ |
|
|
|
|
|
continue |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
log.Fatalf("json.Marshal error %s\n", err) |
|
|
|
|
|
} |
|
|
|
|
|
if err := conn.WriteMessage(websocket.TextMessage, projson); err != nil { |
|
|
|
|
|
break |
|
|
|
|
|
} |
|
|
|
|
|
seconds = current |
|
|
|
|
|
} |
|
|
|
|
|
if strings.Index(content,"100.00%")!=-1{ |
|
|
|
|
|
|
|
|
|
|
|
projson,err := contentToJSONByte(content) |
|
|
|
|
|
if projson==nil && err==nil{ |
|
|
|
|
|
continue |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
log.Fatalf("json.Marshal error %s\n", err) |
|
|
|
|
|
} |
|
|
|
|
|
if err := conn.WriteMessage(websocket.TextMessage, projson); err != nil { |
|
|
|
|
|
panic(err) |
|
|
|
|
|
} |
|
|
|
|
|
break |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
|
|
err = cmd.Wait() |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
log.Fatalf("cmd.Run() failed with %s\n", err) |
|
|
|
|
|
} |
|
|
|
|
|
if errStdout != nil || errStderr != nil { |
|
|
|
|
|
log.Fatalf("failed to capture stdout or stderr\n") |
|
|
|
|
|
} |
|
|
|
|
|
outStr, errStr := string(stdout), string(stderr) |
|
|
|
|
|
fmt.Printf("\nout:\n%s\nerr:\n%s\n", outStr, errStr) |
|
|
|
|
|
|
|
|
|
|
|
defer close(progress) |
|
|
|
|
|
|
|
|
|
|
|
fmt.Println("下载成功") |
|
|
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func contentToJSONByte(content string) ([]byte,error){ |
|
|
|
|
|
sts :=strings.Split(content," ") |
|
|
|
|
|
if len(sts)<8{ |
|
|
|
|
|
return nil,nil |
|
|
|
|
|
} |
|
|
|
|
|
var processFloat float64 |
|
|
|
|
|
if (len(sts)==9 || len(sts)==8){ |
|
|
|
|
|
processFloat,_ =strconv.ParseFloat(strings.Replace(sts[7],"%","",1), 64) |
|
|
|
|
|
}else{ |
|
|
|
|
|
processFloat,_ =strconv.ParseFloat(strings.Replace(sts[8],"%","",1), 64) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if processFloat==0{ |
|
|
|
|
|
return nil,nil |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pro :=&processStruct{ |
|
|
|
|
|
Size:sts[4], |
|
|
|
|
|
CurrentSize: sts[1], |
|
|
|
|
|
Unit: sts[2], |
|
|
|
|
|
Process: processFloat, |
|
|
|
|
|
Hash: "", |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
projson,err :=json.Marshal(pro) |
|
|
|
|
|
|
|
|
|
|
|
return projson,err |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
上传本地文件 |
|
|
|
|
|
@param absolutePath 文件本地绝对路径 |
|
|
|
|
|
@param fileName 文件名称 |
|
|
|
|
|
@param projectName 项目名称 |
|
|
|
|
|
@param dir 云文件目录 |
|
|
|
|
|
*/ |
|
|
|
|
|
func UploadCommand(conn *websocket.Conn,absolutePath,fileName,projectName,dir string) error{ |
|
|
|
|
|
//拷贝文件 |
|
|
|
|
|
|
|
|
|
|
|
//上传文件 |
|
|
|
|
|
|
|
|
|
|
|
// |
|
|
|
|
|
//file,err :=os.Open(absolutePath) |
|
|
|
|
|
//if err != nil{ |
|
|
|
|
|
// return "",err |
|
|
|
|
|
//} |
|
|
|
|
|
// |
|
|
|
|
|
//defer file.Close() |
|
|
|
|
|
// |
|
|
|
|
|
//hash,err :=sh.Add(file) |
|
|
|
|
|
//if err != nil { |
|
|
|
|
|
// return "",err |
|
|
|
|
|
//} |
|
|
|
|
|
|
|
|
|
|
|
//本地拷贝文件 |
|
|
|
|
|
absoluteDir := config.LocalWorkSpaceDir+"\\"+gobalLoginUserId+"\\"+projectName+"\\"+dir |
|
|
|
|
|
//检查目录 |
|
|
|
|
|
_,err := os.Stat(absoluteDir) |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
//创建文件目录 |
|
|
|
|
|
err = os.MkdirAll(absoluteDir, os.ModePerm) |
|
|
|
|
|
if err!=nil{ |
|
|
|
|
|
return err |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
fmt.Println(ipfsPath, "add",absolutePath) |
|
|
|
|
|
cmd := exec.Command(ipfsPath, "add",absolutePath) |
|
|
|
|
|
uploadProgress := make(chan string,10000) |
|
|
|
|
|
var stdout, stderr []byte |
|
|
|
|
|
var errStdout, errStderr error |
|
|
|
|
|
stdoutIn, _ := cmd.StdoutPipe() |
|
|
|
|
|
stderrIn, _ := cmd.StderrPipe() |
|
|
|
|
|
cmd.Start() |
|
|
|
|
|
go func() { |
|
|
|
|
|
stdout, errStdout = copyAndCapture(os.Stdout, stdoutIn, uploadProgress) |
|
|
|
|
|
}() |
|
|
|
|
|
go func() { |
|
|
|
|
|
stderr, errStderr = copyAndCapture(os.Stderr, stderrIn, uploadProgress) |
|
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
|
|
go func(){ |
|
|
|
|
|
seconds := time.Now().Unix() |
|
|
|
|
|
for content := range uploadProgress { // 通道关闭后会退出for range循环 |
|
|
|
|
|
current :=time.Now().Unix() |
|
|
|
|
|
if current-seconds>1{ |
|
|
|
|
|
projson,err := contentToJSONByte(content) |
|
|
|
|
|
if projson==nil && err==nil{ |
|
|
|
|
|
continue |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
log.Fatalf("json.Marshal error %s\n", err) |
|
|
|
|
|
} |
|
|
|
|
|
if err := conn.WriteMessage(websocket.TextMessage, projson); err != nil { |
|
|
|
|
|
break |
|
|
|
|
|
} |
|
|
|
|
|
seconds = current |
|
|
|
|
|
} |
|
|
|
|
|
if strings.Index(content,"100.00%")!=-1{ |
|
|
|
|
|
|
|
|
|
|
|
projson,err := contentToJSONByte(content) |
|
|
|
|
|
if projson==nil && err==nil{ |
|
|
|
|
|
continue |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
log.Fatalf("json.Marshal error %s\n", err) |
|
|
|
|
|
} |
|
|
|
|
|
if err := conn.WriteMessage(websocket.TextMessage, projson); err != nil { |
|
|
|
|
|
panic(err) |
|
|
|
|
|
} |
|
|
|
|
|
break |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
|
|
err = cmd.Wait() |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
log.Fatalf("cmd.Run() failed with %s\n", err) |
|
|
|
|
|
} |
|
|
|
|
|
if errStdout != nil || errStderr != nil { |
|
|
|
|
|
log.Fatalf("failed to capture stdout or stderr\n") |
|
|
|
|
|
} |
|
|
|
|
|
outStr, errStr := string(stdout), string(stderr) |
|
|
|
|
|
fmt.Printf("\nout:\n%s\nerr:\n%s\n", outStr, errStr) |
|
|
|
|
|
/*sh.Get(hash,absoluteDir+fileName) |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
return "",err |
|
|
|
|
|
}*/ |
|
|
|
|
|
defer close(uploadProgress) |
|
|
|
|
|
|
|
|
|
|
|
prog := new(processStruct) |
|
|
|
|
|
prog.Hash=strings.Split(outStr," ")[1] |
|
|
|
|
|
prog.Process=100.00 |
|
|
|
|
|
|
|
|
|
|
|
sh := shell.NewShell(config.GobalIpfsUrl) |
|
|
|
|
|
objectStat,err :=sh.ObjectStat(prog.Hash) |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
return err |
|
|
|
|
|
} |
|
|
|
|
|
fmt.Println(objectStat.CumulativeSize) |
|
|
|
|
|
prog.Size=strconv.Itoa(objectStat.CumulativeSize) |
|
|
|
|
|
|
|
|
|
|
|
projson,err :=json.Marshal(prog) |
|
|
|
|
|
|
|
|
|
|
|
if err := conn.WriteMessage(websocket.TextMessage, projson); err != nil { |
|
|
|
|
|
panic(err) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
go func() { |
|
|
|
|
|
serverSh := shell.NewShell(config.ServerIpfsUrl) |
|
|
|
|
|
err = serverSh.Pin(prog.Hash) |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
fmt.Println(err) |
|
|
|
|
|
} |
|
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
|
|
//本地文件夹拷贝 |
|
|
|
|
|
err = sh.Get(prog.Hash,fmt.Sprint((absoluteDir+"\\"+fileName))) |
|
|
|
|
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
fmt.Println(err) |
|
|
|
|
|
return err |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fmt.Println("上传成功") |
|
|
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func copyAndCapture(w io.Writer, r io.Reader, progress chan string) ([]byte, error) { |
|
|
|
|
|
var out []byte |
|
|
|
|
|
buf := make([]byte, 1024, 1024) |
|
|
|
|
|
for { |
|
|
|
|
|
n, err := r.Read(buf[:]) |
|
|
|
|
|
if n > 0 { |
|
|
|
|
|
d := buf[:n] |
|
|
|
|
|
out = append(out, d...) |
|
|
|
|
|
progress <- string(d) |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
// Read returns io.EOF at the end of file, which is not an error for us |
|
|
|
|
|
if err == io.EOF { |
|
|
|
|
|
err = nil |
|
|
|
|
|
} |
|
|
|
|
|
return out, err |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// never reached |
|
|
|
|
|
panic(true) |
|
|
|
|
|
return nil, nil |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
运行变更生效 |
|
|
|
|
|
@param id 文件id |
|
|
|
|
|
*/ |
|
|
|
|
|
func NotifyFileChange(id string, changeType int){ |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
获取本地文件列表 |
|
|
|
|
|
*/ |
|
|
|
|
|
func SubscriptionFileChange(conn *websocket.Conn, projectName string) error{ |
|
|
|
|
|
|
|
|
|
|
|
getLocalFileListDir = fmt.Sprint(config.LocalWorkSpaceDir+"\\"+gobalLoginUserId+"\\"+projectName+"\\") |
|
|
|
|
|
|
|
|
|
|
|
for { |
|
|
|
|
|
err :=filepath.Walk(getLocalFileListDir,walkfunc) |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
fmt.Println(err) |
|
|
|
|
|
continue |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
mapByte,err:=json.Marshal(gobalFileMap) |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
fmt.Println(err) |
|
|
|
|
|
return err |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if err := conn.WriteMessage(websocket.TextMessage, mapByte); err != nil { |
|
|
|
|
|
fmt.Println(err) |
|
|
|
|
|
} |
|
|
|
|
|
//for k,v := range gobalFileMap { |
|
|
|
|
|
// if v==1{ |
|
|
|
|
|
// //socket |
|
|
|
|
|
// if err := conn.WriteMessage(websocket.TextMessage, []byte(k)); err != nil { |
|
|
|
|
|
// |
|
|
|
|
|
// fmt.Println(err) |
|
|
|
|
|
// continue |
|
|
|
|
|
// } |
|
|
|
|
|
// //添加 检查删除 |
|
|
|
|
|
// //gobalFileMap[k]=0 |
|
|
|
|
|
// } |
|
|
|
|
|
//} |
|
|
|
|
|
fmt.Println("执行睡眠3分钟") |
|
|
|
|
|
|
|
|
|
|
|
time.Sleep(time.Duration(3)*time.Minute) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//err :=filepath.Walk(getLocalFileListDir,walkfunc) |
|
|
|
|
|
//if err != nil { |
|
|
|
|
|
// return gobalFileMap,err |
|
|
|
|
|
//} |
|
|
|
|
|
//fmt.Println(len(gobalFileMap)) |
|
|
|
|
|
//for s := range gobalFileMap { |
|
|
|
|
|
// fmt.Println(s) |
|
|
|
|
|
//} |
|
|
|
|
|
return nil |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func walkfunc(path string, info os.FileInfo, err error) error { |
|
|
|
|
|
|
|
|
|
|
|
if info.IsDir()==false{ |
|
|
|
|
|
|
|
|
|
|
|
sh := shell.NewShell(config.GobalIpfsUrl) |
|
|
|
|
|
file,err :=os.Open(path) |
|
|
|
|
|
if err != nil{ |
|
|
|
|
|
fmt.Println(err) |
|
|
|
|
|
return err |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
defer file.Close() |
|
|
|
|
|
|
|
|
|
|
|
hash,err :=sh.Add(file) |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
fmt.Println(err) |
|
|
|
|
|
return err |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
dir :=strings.Replace(fmt.Sprint(path),fmt.Sprint(getLocalFileListDir),"",1) |
|
|
|
|
|
|
|
|
|
|
|
gobalFileMap[dir]=hash |
|
|
|
|
|
|
|
|
|
|
|
//文件已修改状态 |
|
|
|
|
|
//dir :=strings.Replace(path,getLocalFileListDir,"",1) |
|
|
|
|
|
//if gobalFileMap[dir]==1{ |
|
|
|
|
|
// return nil |
|
|
|
|
|
//} |
|
|
|
|
|
//gobalFileMap[dir]=0 |
|
|
|
|
|
// |
|
|
|
|
|
////判断是否修改 |
|
|
|
|
|
//f,err:=os.Stat(path) |
|
|
|
|
|
//if err!=nil{ |
|
|
|
|
|
// return err |
|
|
|
|
|
//} |
|
|
|
|
|
//secondStr := strconv.FormatInt(f.ModTime().Unix(),10) |
|
|
|
|
|
//if gobalFileUpdateTimeMap[path] == ""{ |
|
|
|
|
|
// gobalFileUpdateTimeMap[path]=secondStr |
|
|
|
|
|
//}else{ |
|
|
|
|
|
// if gobalFileUpdateTimeMap[path]!=secondStr{ |
|
|
|
|
|
// gobalFileUpdateTimeMap[path]=secondStr |
|
|
|
|
|
// gobalFileMap[dir]=1 |
|
|
|
|
|
// } |
|
|
|
|
|
//} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
return nil |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|