d0f3dc806e
- [Apps] This is a feature that has been highly requested by the community. Import the original Docker application into CasaOS. Now it's easy to import with just a few clicks! - [Apps] App list supports a custom sorting function! You can arrange apps in different orders by dragging the icons. - [Apps] App custom installation supports Docker Compose configuration import in YAML format. - [Files] Added thumbnail preview function for image files. - [Connect] Multiple CasaConenct devices in the LAN will be transmitted through the LAN network. - [System] Added a switch for auto-mounting USB disk devices. 🎈 Enhancement - [System] Optimized the system update alert, you will see the new version update log from the next version. - [Apps] Added live preview for icons in custom installed apps. - [Apps] Optimized the input of WebUI. - [Files] Completely updated the image preview, now it supports switching all images in the same folder, as well as dragging, zooming, rotating and resetting. - [Widgets] Added color levels for CPU and RAM charts. - [Conenct] Optimized the display of the right-click menu of the Connect friends list. 🎈 Changed - [Files] Change the initial display directory to /DATA 🐞 Fixed - [System] Fixed an issue with Raspberry Pi devices failing to boot using USB disks. (Achieved by disabling USB disk auto-mount) - [Apps] Fixed the issue that some Docker CLI commands failed to import. - [Apps] Fixed the issue that the app is not easily recognized in /DATA/AppData directory and docker command line after installation, it will be shown as the app name. (Newly installed apps only) - [Apps] Fixed the issue that Pi-hole cannot be launched after installation in the app store. - [Apps] Fixed the issue that apps cannot be updated with WatchTower. - [Files] Fixed the issue that when there is an upload task, the task status is lost after closing Files.
482 lines
12 KiB
Go
482 lines
12 KiB
Go
package service
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"reflect"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/IceWhaleTech/CasaOS/model"
|
|
"github.com/IceWhaleTech/CasaOS/pkg/config"
|
|
"github.com/IceWhaleTech/CasaOS/pkg/quic_helper"
|
|
"github.com/IceWhaleTech/CasaOS/pkg/utils"
|
|
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
|
|
httper2 "github.com/IceWhaleTech/CasaOS/pkg/utils/httper"
|
|
"github.com/IceWhaleTech/CasaOS/pkg/utils/ip_helper"
|
|
port2 "github.com/IceWhaleTech/CasaOS/pkg/utils/port"
|
|
model2 "github.com/IceWhaleTech/CasaOS/service/model"
|
|
"github.com/IceWhaleTech/CasaOS/types"
|
|
"github.com/lucas-clemente/quic-go"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type PersonService interface {
|
|
GetPersionInfo(token string) (m model.PersionModel, err error)
|
|
GetPersionNetWorkTypeDetection() string
|
|
}
|
|
|
|
type personService struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
var IpInfo model.PersionModel
|
|
var CancelList map[string]string
|
|
var InternalInspection map[string][]string
|
|
|
|
func PushIpInfo(token string) {
|
|
|
|
m := model.PersionModel{}
|
|
m.Ips = ip_helper.GetDeviceAllIP("")
|
|
m.Token = token
|
|
b, _ := json.Marshal(m)
|
|
|
|
if reflect.DeepEqual(IpInfo, m) {
|
|
return
|
|
}
|
|
head := make(map[string]string)
|
|
infoS := httper2.Post(config.ServerInfo.Handshake+"/v1/update", b, "application/json", head)
|
|
fmt.Println(infoS)
|
|
}
|
|
func (p *personService) GetPersionInfo(token string) (m model.PersionModel, err error) {
|
|
infoS := httper2.Get(config.ServerInfo.Handshake+"/v1/ips/"+token, nil)
|
|
err = json.Unmarshal([]byte(infoS), &m)
|
|
return
|
|
}
|
|
func (p *personService) GetPersionNetWorkTypeDetection() string {
|
|
data := make(chan string)
|
|
list := []string{"stun.l.google.com", "stun1.l.google.com", "stun2.l.google.com", "stun.sipgate.net"}
|
|
for _, v := range list {
|
|
go utils.GetNetWorkTypeDetection(data, v)
|
|
}
|
|
result := <-data
|
|
close(data)
|
|
return result
|
|
}
|
|
|
|
func NewPersonService(db *gorm.DB) PersonService {
|
|
return &personService{db: db}
|
|
}
|
|
|
|
//=======================================================================================================================================================================
|
|
|
|
var StreamList map[string]quic.Stream
|
|
var ServiceMessage chan model.MessageModel
|
|
|
|
func UDPService() {
|
|
port := 0
|
|
if len(config.ServerInfo.UDPPort) > 0 {
|
|
port, _ = strconv.Atoi(config.ServerInfo.UDPPort)
|
|
if port != 0 && !port2.IsPortAvailable(port, "udp") {
|
|
port = 0
|
|
}
|
|
}
|
|
|
|
srcAddr := &net.UDPAddr{
|
|
IP: net.IPv4zero, Port: port}
|
|
var err error
|
|
UDPConn, err = net.ListenUDP("udp", srcAddr)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
listener, err := quic.Listen(UDPConn, quic_helper.GetGenerateTLSConfig(config.ServerInfo.Token), quic_helper.GetQUICConfig())
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
defer listener.Close()
|
|
ctx := context.Background()
|
|
acceptFailures := 0
|
|
const maxAcceptFailures = 10
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
fmt.Println(ctx.Err())
|
|
return
|
|
default:
|
|
}
|
|
|
|
session, err := listener.Accept(ctx)
|
|
if err != nil {
|
|
fmt.Println("Listen (BEP/quic): Accepting connection:", err)
|
|
|
|
acceptFailures++
|
|
if acceptFailures > maxAcceptFailures {
|
|
// Return to restart the listener, because something
|
|
// seems permanently damaged.
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
|
|
// Slightly increased delay for each failure.
|
|
time.Sleep(time.Duration(acceptFailures) * time.Second)
|
|
|
|
continue
|
|
}
|
|
|
|
acceptFailures = 0
|
|
|
|
streamCtx, cancel := context.WithTimeout(ctx, time.Second*10)
|
|
stream, err := session.AcceptStream(streamCtx)
|
|
cancel()
|
|
if err != nil {
|
|
fmt.Println("failed to accept stream from %s: %v", session.RemoteAddr(), err)
|
|
_ = session.CloseWithError(1, err.Error())
|
|
continue
|
|
}
|
|
|
|
// prefixByte := make([]byte, 4)
|
|
// c1, err := io.ReadFull(stream, prefixByte)
|
|
// fmt.Println(c1, err)
|
|
// prefixLength, err := strconv.Atoi(string(prefixByte))
|
|
// if err != nil {
|
|
// fmt.Println(err)
|
|
// }
|
|
// messageByte := make([]byte, prefixLength)
|
|
// t, err := io.ReadFull(stream, messageByte)
|
|
// fmt.Println(t, err)
|
|
// m := model.MessageModel{}
|
|
// err = json.Unmarshal(messageByte, &m)
|
|
// if err != nil {
|
|
// fmt.Println(err)
|
|
// }
|
|
|
|
go ProcessingContent(stream)
|
|
}
|
|
}
|
|
|
|
//处理内容
|
|
func ProcessingContent(stream quic.Stream) {
|
|
for {
|
|
prefixByte := make([]byte, 6)
|
|
_, err := io.ReadFull(stream, prefixByte)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
prefixLength, err := strconv.Atoi(string(prefixByte))
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
messageByte := make([]byte, prefixLength)
|
|
_, err = io.ReadFull(stream, messageByte)
|
|
if err != nil {
|
|
return
|
|
}
|
|
m := model.MessageModel{}
|
|
err = json.Unmarshal(messageByte, &m)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
if m.Type == types.PERSONHELLO {
|
|
//nothing
|
|
continue
|
|
} else if m.Type == types.PERSONDIRECTORY {
|
|
friend := model2.FriendModel{}
|
|
friend.Token = m.From
|
|
var list []model.Path
|
|
rFriend := MyService.Friend().GetFriendById(friend)
|
|
if !reflect.DeepEqual(rFriend, model2.FriendModel{Token: m.From}) && !rFriend.Block {
|
|
if m.Data.(string) == "" || m.Data.(string) == "/" {
|
|
for _, v := range config.FileSettingInfo.ShareDir {
|
|
//tempList := MyService.ZiMa().GetDirPath(v)
|
|
temp := MyService.ZiMa().GetDirPathOne(v)
|
|
list = append(list, temp)
|
|
}
|
|
} else {
|
|
list = MyService.ZiMa().GetDirPath(m.Data.(string))
|
|
}
|
|
} else {
|
|
list = []model.Path{}
|
|
}
|
|
if rFriend.Write {
|
|
for i := 0; i < len(list); i++ {
|
|
list[i].Write = true
|
|
}
|
|
}
|
|
m.To = m.From
|
|
m.Data = list
|
|
m.From = config.ServerInfo.Token
|
|
SendData(stream, m)
|
|
break
|
|
} else if m.Type == types.PERSONDOWNLOAD {
|
|
|
|
SendFileData(stream, m.Data.(string), m.From, m.UUId, types.PERSONDOWNLOAD)
|
|
break
|
|
} else if m.Type == types.PERSONADDFRIEND {
|
|
friend := model2.FriendModel{}
|
|
dataModelByte, _ := json.Marshal(m.Data)
|
|
err := json.Unmarshal(dataModelByte, &friend)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
go MyService.Friend().UpdateOrCreate(friend)
|
|
mi := model2.FriendModel{}
|
|
mi.Avatar = config.UserInfo.Avatar
|
|
mi.Profile = config.UserInfo.Description
|
|
mi.NickName = config.UserInfo.NickName
|
|
m.To = m.From
|
|
m.Data = mi
|
|
m.Type = types.PERSONADDFRIEND
|
|
m.From = config.ServerInfo.Token
|
|
|
|
SendData(stream, m)
|
|
break
|
|
} else if m.Type == types.PERSONCONNECTION {
|
|
if len(m.Data.(string)) > 0 {
|
|
fmt.Println("设置ip", m.Data.(string))
|
|
UDPAddressMap[m.From] = m.Data.(string)
|
|
} else {
|
|
delete(UDPAddressMap, m.From)
|
|
}
|
|
// mi := model2.FriendModel{}
|
|
// mi.Avatar = config.UserInfo.Avatar
|
|
// mi.Profile = config.UserInfo.Description
|
|
// mi.NickName = config.UserInfo.NickName
|
|
// mi.Token = config.ServerInfo.Token
|
|
|
|
user := MyService.Casa().GetUserInfoByShareId(m.From)
|
|
//好友申请 //不是好友
|
|
friend := model2.FriendModel{}
|
|
friend.Token = m.From
|
|
friend.Avatar = user.Avatar
|
|
friend.Block = false
|
|
friend.NickName = user.NickName
|
|
friend.Profile = user.Avatar
|
|
friend.Write = false
|
|
friend.Version = user.Version
|
|
if len(config.UserInfo.Public) > 0 {
|
|
friend.State = types.FRIENDSTATEREQUEST
|
|
}
|
|
MyService.Friend().AddFriend(friend)
|
|
|
|
msg := model.MessageModel{}
|
|
msg.Type = types.PERSONHELLO
|
|
msg.Data = ""
|
|
msg.To = m.From
|
|
msg.From = config.ServerInfo.Token
|
|
msg.UUId = m.UUId
|
|
Dial(msg, false)
|
|
|
|
//agree user
|
|
if len(config.UserInfo.Public) == 0 {
|
|
msg.Type = types.PERSONAGREEFRIEND
|
|
msg.Data = ""
|
|
msg.To = m.From
|
|
msg.From = config.ServerInfo.Token
|
|
msg.UUId = m.UUId
|
|
Dial(msg, true)
|
|
}
|
|
break
|
|
} else if m.Type == types.PERSONAGREEFRIEND {
|
|
MyService.Friend().AgreeFrined(m.From)
|
|
break
|
|
} else if m.Type == types.PERSONCANCEL {
|
|
CancelList[m.UUId] = "cancel"
|
|
break
|
|
} else if m.Type == types.PERSONSUMMARY {
|
|
Summary(m, "upload")
|
|
continue
|
|
} else if m.Type == types.PERSONUPLOAD {
|
|
//TODO:检查是否存在如果存在直接结束
|
|
task := model2.PersonDownloadDBModel{}
|
|
task.UUID = m.UUId
|
|
task.LocalPath = m.Data.(string)
|
|
MyService.Download().AddDownloadTask(task)
|
|
friend := MyService.Friend().GetFriendById(model2.FriendModel{Token: m.From})
|
|
if friend.Write {
|
|
continue
|
|
} else {
|
|
break
|
|
}
|
|
} else if m.Type == types.PERSONUPLOADDATA {
|
|
r := SaveFile(m, stream)
|
|
if r {
|
|
break
|
|
}
|
|
continue
|
|
} else if m.Type == types.PERSONINTERNALINSPECTION {
|
|
fmt.Println("内网测试")
|
|
var ips []string
|
|
dataModelByte, _ := json.Marshal(m.Data)
|
|
err := json.Unmarshal(dataModelByte, &ips)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
break
|
|
}
|
|
|
|
go MyService.Friend().InternalInspection(ips, m.From)
|
|
|
|
} else if m.Type == types.PERSONPING {
|
|
fmt.Println("来自", m.From, "的ping", m.Data)
|
|
msg := m
|
|
m.To = m.From
|
|
m.Data = config.ServerInfo.Token
|
|
m.From = config.ServerInfo.Token
|
|
SendData(stream, m)
|
|
|
|
var ips []string
|
|
dataModelByte, _ := json.Marshal(msg.Data)
|
|
err := json.Unmarshal(dataModelByte, &ips)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
break
|
|
}
|
|
backIP := false
|
|
if v, ok := UDPAddressMap[msg.From]; ok {
|
|
for _, ip := range ips {
|
|
if ip == v {
|
|
backIP = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !backIP {
|
|
fmt.Println("检测需要查询ip", msg.From)
|
|
go MyService.Friend().InternalInspection(ips, msg.From)
|
|
}
|
|
|
|
break
|
|
} else if m.Type == types.PERSONIMAGETHUMBNAIL {
|
|
m.To = m.From
|
|
|
|
if data, err := file.GetImage(m.Data.(string), 100, 0); err == nil {
|
|
m.Data = data
|
|
} else {
|
|
m.Data = ""
|
|
}
|
|
m.From = config.ServerInfo.Token
|
|
SendData(stream, m)
|
|
break
|
|
} else {
|
|
//不应有不做返回的数据
|
|
//ServiceMessage <- m
|
|
break
|
|
}
|
|
}
|
|
stream.Close()
|
|
|
|
}
|
|
|
|
//文件分片发送
|
|
func SendFileData(stream quic.Stream, filePath, to, uuid, t string) error {
|
|
summary := model.FileSummaryModel{}
|
|
|
|
msg := model.MessageModel{}
|
|
msg.Type = types.PERSONSUMMARY
|
|
msg.From = config.ServerInfo.Token
|
|
msg.To = to
|
|
msg.UUId = uuid
|
|
|
|
fStat, err := os.Stat(filePath)
|
|
if err != nil {
|
|
|
|
summary.Message = err.Error()
|
|
|
|
msg.Data = summary
|
|
|
|
summaryByte, _ := json.Marshal(msg)
|
|
summaryPrefixLength := file.PrefixLength(len(summaryByte))
|
|
summaryData := append(summaryPrefixLength, summaryByte...)
|
|
stream.Write(summaryData)
|
|
return err
|
|
}
|
|
|
|
blockSize, length := file.GetBlockInfo(fStat.Size())
|
|
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
|
|
summary.Message = err.Error()
|
|
msg.Data = summary
|
|
|
|
summaryByte, _ := json.Marshal(msg)
|
|
summaryPrefixLength := file.PrefixLength(len(summaryByte))
|
|
summaryData := append(summaryPrefixLength, summaryByte...)
|
|
stream.Write(summaryData)
|
|
return err
|
|
}
|
|
|
|
//send file summary first
|
|
summary.BlockSize = blockSize
|
|
summary.Hash = file.GetHashByPath(filePath)
|
|
summary.Length = length
|
|
summary.Name = fStat.Name()
|
|
summary.Size = fStat.Size()
|
|
|
|
msg.Data = summary
|
|
|
|
summaryByte, _ := json.Marshal(msg)
|
|
summaryPrefixLength := file.PrefixLength(len(summaryByte))
|
|
summaryData := append(summaryPrefixLength, summaryByte...)
|
|
stream.Write(summaryData)
|
|
|
|
bufferedReader := bufio.NewReader(f)
|
|
buf := make([]byte, blockSize)
|
|
|
|
defer stream.Close()
|
|
|
|
for i := 0; i < length; i++ {
|
|
|
|
tran := model.TranFileModel{}
|
|
|
|
n, err := bufferedReader.Read(buf)
|
|
|
|
if err == io.EOF {
|
|
fmt.Println("读取完毕", err)
|
|
}
|
|
|
|
tran.Hash = file.GetHashByContent(buf[:n])
|
|
tran.Index = i
|
|
tran.Length = length
|
|
|
|
fileMsg := model.MessageModel{}
|
|
fileMsg.Type = t
|
|
fileMsg.Data = tran
|
|
fileMsg.From = config.ServerInfo.Token
|
|
fileMsg.To = to
|
|
fileMsg.UUId = uuid
|
|
b, _ := json.Marshal(fileMsg)
|
|
prefixLength := file.PrefixLength(len(b))
|
|
dataLength := file.DataLength(len(buf[:n]))
|
|
data := append(append(append(prefixLength, b...), dataLength...), buf[:n]...)
|
|
if _, ok := CancelList[uuid]; ok {
|
|
delete(CancelList, uuid)
|
|
return nil
|
|
}
|
|
stream.Write(data)
|
|
}
|
|
record := model2.PersonDownRecordDBModel{}
|
|
record.UUID = uuid
|
|
record.Name = f.Name()
|
|
record.Downloader = to
|
|
record.Path = filePath
|
|
record.Size = fStat.Size()
|
|
record.Type = types.PERSONFILEDOWNLOAD
|
|
if t == types.PERSONUPLOADDATA {
|
|
record.Type = types.PERSONFILEUPLOAD
|
|
}
|
|
|
|
MyService.DownRecord().AddDownRecord(record)
|
|
|
|
return nil
|
|
}
|