浏览代码

Merge pull request #148 from IceWhaleTech/dev

Dev
link 3 年之前
父节点
当前提交
2eff7fa8bc
共有 100 个文件被更改,包括 2715 次插入1758 次删除
  1. 1 1
      UI
  2. 9 6
      conf/conf.ini.sample
  3. 1 0
      go.mod
  4. 1 0
      go.sum
  5. 43 4
      main.go
  6. 8 0
      model/app-analyse.go
  7. 13 15
      model/net.go
  8. 8 1
      model/person.go
  9. 16 13
      model/sys_common.go
  10. 9 0
      model/user.go
  11. 4 4
      model/zima.go
  12. 3 4
      pkg/config/init.go
  13. 19 2
      pkg/config/update.go
  14. 51 0
      pkg/quic_helper/config.go
  15. 1 1
      pkg/sqlite/db.go
  16. 2 2
      pkg/utils/file/block.go
  17. 13 0
      pkg/utils/file/file.go
  18. 3 1
      pkg/utils/httper/httper.go
  19. 0 1
      pkg/utils/ini_helper.go
  20. 18 0
      pkg/utils/oasis_err/e.go
  21. 0 47
      pkg/zerotier/zerotier_api.go
  22. 50 3
      route/init.go
  23. 33 59
      route/route.go
  24. 1 1
      route/v1/app.go
  25. 49 4
      route/v1/file.go
  26. 444 33
      route/v1/persion.go
  27. 55 1
      route/v1/system.go
  28. 107 23
      route/v1/user.go
  29. 0 475
      route/v1/zerotier.go
  30. 1 1
      route/v1/zima_info.go
  31. 65 1
      service/casa.go
  32. 66 0
      service/download.go
  33. 64 0
      service/friend.go
  34. 18 18
      service/model/o_download.go
  35. 19 0
      service/model/o_friend.go
  36. 21 0
      service/notify.go
  37. 247 399
      service/person.go
  38. 12 6
      service/service.go
  39. 0 74
      service/socket.go
  40. 16 0
      service/system.go
  41. 284 89
      service/udpconn.go
  42. 6 2
      service/user.go
  43. 0 353
      service/zerotier.go
  44. 69 2
      service/zima_info.go
  45. 5 10
      shell/assist.sh
  46. 18 1
      shell/helper.sh
  47. 5 2
      shell/usb-mount.sh
  48. 2 0
      types/notify.go
  49. 9 0
      types/person.go
  50. 1 0
      types/person_download.go
  51. 2 2
      types/system.go
  52. 96 0
      web/8ee7a98310ee94717fe1.worker.js
  53. 二进制
      web/img/1-small.1b74d2ba.png
  54. 二进制
      web/img/Account.1bc4a418.png
  55. 0 72
      web/img/Account.bde47fba.svg
  56. 17 0
      web/img/android-package-archive.c32c4fdb.svg
  57. 25 25
      web/img/android.149f5693.svg
  58. 17 0
      web/img/application-apk.319706c4.svg
  59. 21 0
      web/img/application-certificate.64a6804d.svg
  60. 11 0
      web/img/application-dicom.fbef31c7.svg
  61. 13 0
      web/img/application-epub+zip.73eef8b2.svg
  62. 13 0
      web/img/application-illustrator.2ea791ae.svg
  63. 22 0
      web/img/application-json.eea7b6c3.svg
  64. 13 0
      web/img/application-msonenote.4772ddbe.svg
  65. 18 0
      web/img/application-msoutlook.0c2789ef.svg
  66. 16 0
      web/img/application-msword-template.5500dd0c.svg
  67. 26 0
      web/img/application-octet-stream.4dbc6148.svg
  68. 21 0
      web/img/application-ogg.776137df.svg
  69. 13 0
      web/img/application-pdf.319df017.svg
  70. 13 0
      web/img/application-pgp-signature.a482d859.svg
  71. 11 0
      web/img/application-pgp.ef9a65ff.svg
  72. 13 0
      web/img/application-photoshop.20ed858b.svg
  73. 13 0
      web/img/application-postscript.7a2e596a.svg
  74. 14 0
      web/img/application-rss_xml.5d705f41.svg
  75. 16 0
      web/img/application-sql.e76bf92b.svg
  76. 19 0
      web/img/application-vnd.appimage.50730ff3.svg
  77. 13 0
      web/img/application-vnd.flatpak.8a781de6.svg
  78. 21 0
      web/img/application-vnd.flatpak.ref.26e20d79.svg
  79. 13 0
      web/img/application-vnd.iccprofile.0c017bee.svg
  80. 29 0
      web/img/application-vnd.kde.bluedevil-sendfile.a912311c.svg
  81. 13 0
      web/img/application-vnd.ms-access.785a7627.svg
  82. 32 0
      web/img/application-vnd.ms-cab-compressed.011f2e86.svg
  83. 16 0
      web/img/application-vnd.ms-excel.ec28c59f.svg
  84. 16 0
      web/img/application-vnd.ms-excel.template.macroenabled.12.8ee75839.svg
  85. 24 0
      web/img/application-vnd.ms-htmlhelp.c8fb9bfd.svg
  86. 13 0
      web/img/application-vnd.ms-infopath.c0a76ad3.svg
  87. 16 0
      web/img/application-vnd.ms-powerpoint.b1a13336.svg
  88. 16 0
      web/img/application-vnd.ms-powerpoint.template.macroenabled.12.0409b2fe.svg
  89. 13 0
      web/img/application-vnd.ms-publisher.32dbb1c3.svg
  90. 16 0
      web/img/application-vnd.ms-word.0209c533.svg
  91. 13 0
      web/img/application-vnd.oasis.opendocument.chart.91c53f93.svg
  92. 13 0
      web/img/application-vnd.oasis.opendocument.formula-template.d5dbf3d8.svg
  93. 13 0
      web/img/application-vnd.oasis.opendocument.formula.a4bc6018.svg
  94. 13 0
      web/img/application-vnd.oasis.opendocument.presentation-template.5b2c8be4.svg
  95. 13 0
      web/img/application-vnd.oasis.opendocument.spreadsheet-template.a3256fa8.svg
  96. 13 0
      web/img/application-vnd.oasis.opendocument.text-template.4e634bfe.svg
  97. 11 0
      web/img/application-vnd.oasis.opendocument.web-template.5d282765.svg
  98. 13 0
      web/img/application-vnd.snap.72d9809b.svg
  99. 13 0
      web/img/application-vnd.visio.90d89058.svg
  100. 16 0
      web/img/application-x-ace.03fa0685.svg

+ 1 - 1
UI

@@ -1 +1 @@
-Subproject commit 247c099bf14a2d9eb94bf7798e04d00dbc8f7efd
+Subproject commit 74fa1f8920aa23f40b04b87cc04ebef5c36b0890

+ 9 - 6
conf/conf.ini.sample

@@ -9,14 +9,17 @@ DateTimeFormat = 2006-01-02 15:04:05
 TimeFormat = 15:04:05
 DateFormat = 2006-01-02
 ProjectPath = /casaOS/server
+RootPath = /casaOS
 
 
 [server]
 HttpPort = 8089
+UDPPort = 
 RunMode = release
 ServerApi = https://api.casaos.zimaboard.com
-Handshake = 
+Handshake = socket.casaos.io
 Token = 
+USBAutoMount = true
 
 
 [user]
@@ -25,11 +28,8 @@ PWD = zimaboard
 Email = user@gmail.com
 Description = description
 Initialized = false
-
-[zerotier]
-UserName = user
-PWD = pwd
-Token = yBKYyavr2RdFAIVN7iTpzlsB1o6CqTgm
+Avatar = 
+NickName = 
 
 [redis]
 Host = 127.0.0.1:6379
@@ -43,3 +43,6 @@ ConfigStr =
 WidgetList =
 Analyse =
 
+[file]
+ShareDir =
+DownloadDir =

+ 1 - 0
go.mod

@@ -50,6 +50,7 @@ require (
 	github.com/sirupsen/logrus v1.8.1
 	github.com/smartystreets/assertions v1.2.0 // indirect
 	github.com/smartystreets/goconvey v1.6.4 // indirect
+	github.com/spf13/afero v1.2.2
 	github.com/swaggo/gin-swagger v1.3.0
 	github.com/swaggo/swag v1.7.3
 	github.com/tidwall/gjson v1.10.2

+ 1 - 0
go.sum

@@ -794,6 +794,7 @@ github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:Udh
 github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
 github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=

+ 43 - 4
main.go

@@ -4,6 +4,7 @@ import (
 	"flag"
 	"fmt"
 	"net/http"
+	"runtime"
 	"time"
 
 	"github.com/IceWhaleTech/CasaOS/pkg/cache"
@@ -28,14 +29,40 @@ func init() {
 	config.InitSetup(*configFlag)
 	config.UpdateSetup()
 	loger2.LogSetup()
+	sysType := runtime.GOOS
+	if sysType == "windows" {
+		config.AppInfo.ProjectPath = "C:\\CasaOS\\service"
+		config.Cfg.Section("app").Key("ProjectPath").SetValue("C:\\CasaOS\\service")
+
+		config.AppInfo.RootPath = "C:\\CasaOS"
+		config.Cfg.Section("app").Key("RootPath").SetValue("C:\\CasaOS")
+		config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
+	}
+	if sysType == "darwin" {
+		config.AppInfo.ProjectPath = "./CasaOS/service"
+		config.Cfg.Section("app").Key("ProjectPath").SetValue("./CasaOS/service")
+
+		config.AppInfo.RootPath = "./CasaOS"
+		config.Cfg.Section("app").Key("RootPath").SetValue("./CasaOS")
+		config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
+	}
+
 	sqliteDB = sqlite.GetDb(config.AppInfo.ProjectPath)
 	//gredis.GetRedisConn(config.RedisInfo),
 	service.MyService = service.NewService(sqliteDB, loger2.NewOLoger())
 	service.Cache = cache.Init()
-	//go service.UDPConnect([]string{})
+
+	go service.UDPService()
+
+	fmt.Println("token", service.GetToken())
+	service.UDPAddressMap = make(map[string]string)
 	//go service.SocketConnect()
+	service.CancelList = make(map[string]string)
 	route.InitFunction()
 
+	go service.SendIPToServer()
+	go service.LoopFriend()
+
 }
 
 // @title casaOS API
@@ -60,18 +87,30 @@ func main() {
 	//gredis.Setup()
 	r := route.InitRouter()
 	//service.SyncTask(sqliteDB)
-	cron2 := cron.New() //创建一个cron实例
+	cron2 := cron.New()
 	//every day execution
-	err := cron2.AddFunc("0 0/1 * * * *", func() {
+	err := cron2.AddFunc("0 0/5 * * * *", func() {
 		//service.PushIpInfo(*&config.ServerInfo.Token)
 		//service.UpdataDDNSList(mysqldb)
 		//service.SyncTask(sqliteDB)
+
+		service.SendIPToServer()
+
+		service.LoopFriend()
+
 	})
 	if err != nil {
 		fmt.Println(err)
 	}
+	// err = cron2.AddFunc("0/1 * * * * *", func() {
+
+	// 	//service.SendIPToServer()
+	// 	//service.LoopNet()
 
-	//启动/关闭
+	// })
+	// if err != nil {
+	// 	fmt.Println(err)
+	// }
 	cron2.Start()
 	defer cron2.Stop()
 	s := &http.Server{

+ 8 - 0
model/app-analyse.go

@@ -6,3 +6,11 @@ type AppAnalyse struct {
 	UUId     string `json:"uuid"`
 	Language string `json:"language"`
 }
+
+type ConnectionStatus struct {
+	From  string `json:"from"`
+	To    string `json:"to"`
+	Error string `json:"error"`
+	UUId  string `json:"uuid"`
+	Event string `json:"event"`
+}

+ 13 - 15
model/net.go

@@ -1,19 +1,17 @@
 package model
 
-import "time"
-
 type IOCountersStat struct {
-	Name        string    `json:"name"`        // interface name
-	BytesSent   uint64    `json:"bytesSent"`   // number of bytes sent
-	BytesRecv   uint64    `json:"bytesRecv"`   // number of bytes received
-	PacketsSent uint64    `json:"packetsSent"` // number of packets sent
-	PacketsRecv uint64    `json:"packetsRecv"` // number of packets received
-	Errin       uint64    `json:"errin"`       // total number of errors while receiving
-	Errout      uint64    `json:"errout"`      // total number of errors while sending
-	Dropin      uint64    `json:"dropin"`      // total number of incoming packets which were dropped
-	Dropout     uint64    `json:"dropout"`     // total number of outgoing packets which were dropped (always 0 on OSX and BSD)
-	Fifoin      uint64    `json:"fifoin"`      // total number of FIFO buffers errors while receiving
-	Fifoout     uint64    `json:"fifoout"`     // total number of FIFO buffers errors while sending
-	State       string    `json:"state"`
-	DateTime    time.Time `json:"date_time"`
+	Name        string `json:"name"`        // interface name
+	BytesSent   uint64 `json:"bytesSent"`   // number of bytes sent
+	BytesRecv   uint64 `json:"bytesRecv"`   // number of bytes received
+	PacketsSent uint64 `json:"packetsSent"` // number of packets sent
+	PacketsRecv uint64 `json:"packetsRecv"` // number of packets received
+	Errin       uint64 `json:"errin"`       // total number of errors while receiving
+	Errout      uint64 `json:"errout"`      // total number of errors while sending
+	Dropin      uint64 `json:"dropin"`      // total number of incoming packets which were dropped
+	Dropout     uint64 `json:"dropout"`     // total number of outgoing packets which were dropped (always 0 on OSX and BSD)
+	Fifoin      uint64 `json:"fifoin"`      // total number of FIFO buffers errors while receiving
+	Fifoout     uint64 `json:"fifoout"`     // total number of FIFO buffers errors while sending
+	State       string `json:"state"`
+	Time        int64  `json:"time"`
 }

+ 8 - 1
model/person.go

@@ -40,8 +40,15 @@ type FileDetailModel struct {
 type FileSummaryModel struct {
 	Hash      string `json:"hash"` //Verify file
 	Name      string `json:"name"`
-	Path      string `json:"path"`
 	BlockSize int    `json:"block_size"`
 	Length    int    `json:"length"`
 	Size      int64  `json:"size"`
+	Message   string `json:"message"`
+}
+
+type FriendsModel struct {
+	Id       uint   `gorm:"column:id;primary_key" json:"id"`
+	NickName string `json:"nick_name"`
+	Desc     string `json:"desc"`
+	ShareId  string `json:"share_id"`
 }

+ 16 - 13
model/sys_common.go

@@ -16,16 +16,20 @@ type UserModel struct {
 	Email       string
 	Description string
 	Initialized bool
+	Avatar      string
+	NickName    string
 }
 
 //服务配置
 type ServerModel struct {
-	HttpPort    string
-	RunMode     string
-	ServerApi   string
-	LockAccount bool
-	Handshake   string
-	Token       string
+	HttpPort     string
+	RunMode      string
+	ServerApi    string
+	LockAccount  bool
+	Handshake    string
+	Token        string
+	UDPPort      string
+	USBAutoMount string
 }
 
 //服务配置
@@ -38,6 +42,7 @@ type APPModel struct {
 	TimeFormat     string
 	DateFormat     string
 	ProjectPath    string
+	RootPath       string
 }
 
 //公共返回模型
@@ -47,13 +52,6 @@ type Result struct {
 	Data    interface{} `json:"data" example:"返回结果"`
 }
 
-//zeritier相关
-type ZeroTierModel struct {
-	UserName string
-	PWD      string
-	Token    string
-}
-
 //redis配置文件
 type RedisModel struct {
 	Host        string
@@ -75,3 +73,8 @@ type SystemConfig struct {
 type CasaOSGlobalVariables struct {
 	AppChange bool
 }
+
+type FileSetting struct {
+	ShareDir    []string `json:"share_dir" delim:"|"`
+	DownloadDir string   `json:"download_dir"`
+}

+ 9 - 0
model/user.go

@@ -0,0 +1,9 @@
+package model
+
+type UserInfo struct {
+	NickName string `json:"nick_name"`
+	Desc     string `json:"desc"`
+	ShareId  string `json:"share_id"`
+	Avatar   string `json:"avatar"`
+	Version  int    `json:"version,omitempty"`
+}

+ 4 - 4
model/zima.go

@@ -3,11 +3,11 @@ package model
 import "time"
 
 type Path struct {
-	Name  string    `json:"name"`
-	Path  string    `json:"path"`
-	IsDir bool      `json:"is_dir"`
+	Name  string    `json:"name"`   //File name or document name
+	Path  string    `json:"path"`   //Full path to file or folder
+	IsDir bool      `json:"is_dir"` //Is it a folder
 	Date  time.Time `json:"date"`
-	Size  int64     `json:"size"`
+	Size  int64     `json:"size"` //File Size
 	Type  string    `json:"type,omitempty"`
 	Label string    `json:"label,omitempty"`
 }

+ 3 - 4
pkg/config/init.go

@@ -25,9 +25,6 @@ var AppInfo = &model.APPModel{}
 //redis相关配置
 var RedisInfo = &model.RedisModel{}
 
-//zerotier相关
-var ZeroTierInfo = &model.ZeroTierModel{}
-
 //server相关
 var ServerInfo = &model.ServerModel{}
 
@@ -35,6 +32,8 @@ var SystemConfigInfo = &model.SystemConfig{}
 
 var CasaOSGlobalVariables = &model.CasaOSGlobalVariables{}
 
+var FileSettingInfo = &model.FileSetting{}
+
 var Cfg *ini.File
 
 //初始化设置,获取系统的部分信息。
@@ -54,10 +53,10 @@ func InitSetup(config string) {
 
 	mapTo("user", UserInfo)
 	mapTo("app", AppInfo)
-	mapTo("zerotier", ZeroTierInfo)
 	mapTo("redis", RedisInfo)
 	mapTo("server", ServerInfo)
 	mapTo("system", SystemConfigInfo)
+	mapTo("file", FileSettingInfo)
 	SystemConfigInfo.ConfigPath = configDir
 	//	AppInfo.ProjectPath = getCurrentDirectory() //os.Getwd()
 

+ 19 - 2
pkg/config/update.go

@@ -1,13 +1,30 @@
 package config
 
-import "github.com/IceWhaleTech/CasaOS/pkg/utils/file"
+import (
+	"runtime"
+
+	"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
+)
 
 //检查目录是否存在
 func mkdirDATAAll() {
-	dirArray := [7]string{"/DATA/AppData", "/DATA/Documents", "/DATA/Downloads", "/DATA/Gallery", "/DATA/Media/Movies", "/DATA/Media/TV Shows", "/DATA/Media/Music"}
+	sysType := runtime.GOOS
+	var dirArray []string
+	if sysType == "linux" {
+		dirArray = []string{"/DATA/AppData", "/DATA/Documents", "/DATA/Downloads", "/DATA/Gallery", "/DATA/Media/Movies", "/DATA/Media/TV Shows", "/DATA/Media/Music"}
+	}
+
+	if sysType == "windows" {
+		dirArray = []string{"C:\\CasaOS\\DATA\\AppData", "C:\\CasaOS\\DATA\\Documents", "C:\\CasaOS\\DATA\\Downloads", "C:\\CasaOS\\DATA\\Gallery", "C:\\CasaOS\\DATA\\Media/Movies", "C:\\CasaOS\\DATA\\Media\\TV Shows", "C:\\CasaOS\\DATA\\Media\\Music"}
+	}
+	if sysType == "darwin" {
+		dirArray = []string{"./CasaOS/DATA/AppData", "./CasaOS/DATA/Documents", "./CasaOS/DATA/Downloads", "./CasaOS/DATA/Gallery", "./CasaOS/DATA/Media/Movies", "./CasaOS/DATA/Media/TV Shows", "./CasaOS/DATA/Media/Music"}
+	}
+
 	for _, v := range dirArray {
 		file.IsNotExistMkDir(v)
 	}
+
 }
 
 func UpdateSetup() {

+ 51 - 0
pkg/quic_helper/config.go

@@ -0,0 +1,51 @@
+package quic_helper
+
+import (
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/tls"
+	"crypto/x509"
+	"encoding/pem"
+	"math/big"
+
+	"github.com/lucas-clemente/quic-go"
+)
+
+// Setup a bare-bones TLS config for the server
+func GetGenerateTLSConfig(token string) *tls.Config {
+	key, err := rsa.GenerateKey(rand.Reader, 1024)
+	if err != nil {
+		panic(err)
+	}
+	template := x509.Certificate{SerialNumber: big.NewInt(1)}
+	certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
+	if err != nil {
+		panic(err)
+	}
+	keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
+	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
+
+	tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
+	if err != nil {
+		panic(err)
+	}
+	return &tls.Config{
+		Certificates:           []tls.Certificate{tlsCert},
+		NextProtos:             []string{token},
+		SessionTicketsDisabled: true,
+	}
+}
+func GetClientTlsConfig(otherToken string) *tls.Config {
+	return &tls.Config{
+		InsecureSkipVerify:     true,
+		NextProtos:             []string{otherToken},
+		SessionTicketsDisabled: true,
+	}
+}
+
+func GetQUICConfig() *quic.Config {
+	return &quic.Config{
+		ConnectionIDLength: 4,
+		KeepAlive:          true,
+	}
+}

+ 1 - 1
pkg/sqlite/db.go

@@ -31,7 +31,7 @@ func GetDb(projectPath string) *gorm.DB {
 		return nil
 	}
 	gdb = db
-	err = db.AutoMigrate(&model2.TaskDBModel{}, &model2.AppNotify{}, &model2.AppListDBModel{}, &model2.SerialDisk{}, model2.PersionDownloadDBModel{})
+	err = db.AutoMigrate(&model2.TaskDBModel{}, &model2.AppNotify{}, &model2.AppListDBModel{}, &model2.SerialDisk{}, model2.PersonDownloadDBModel{}, model2.FriendModel{})
 	if err != nil {
 		fmt.Println("检查和创建数据库出错", err)
 	}

+ 2 - 2
pkg/utils/file/block.go

@@ -60,9 +60,9 @@ func ComparisonHash(data []byte, hash string) bool {
 
 //get prefix byte length
 func PrefixLength(byteLength int) []byte {
-	lengthByte := []byte{'0', '0', '0', '0'}
+	lengthByte := []byte{'0', '0', '0', '0', '0', '0'}
 	bSize := strconv.Itoa(byteLength)
-	cha := 4 - len(bSize)
+	cha := 6 - len(bSize)
 	for i := len(bSize); i > 0; i-- {
 		lengthByte[cha+i-1] = bSize[i-1]
 	}

+ 13 - 0
pkg/utils/file/file.go

@@ -8,6 +8,8 @@ import (
 	"mime/multipart"
 	"os"
 	"path"
+	path2 "path"
+	"path/filepath"
 	"strconv"
 	"strings"
 )
@@ -226,6 +228,17 @@ func CopyFile(src, dst string) error {
 	return os.Chmod(dst, srcinfo.Mode())
 }
 
+//Check for duplicate file names
+func GetNoDuplicateFileName(fullPath string) string {
+	path, fileName := filepath.Split(fullPath)
+	fileSuffix := path2.Ext(fileName)
+	filenameOnly := strings.TrimSuffix(fileName, fileSuffix)
+	for i := 0; Exists(fullPath); i++ {
+		fullPath = path2.Join(path, filenameOnly+"("+strconv.Itoa(i+1)+")"+fileSuffix)
+	}
+	return fullPath
+}
+
 // Dir copies a whole directory recursively
 func CopyDir(src string, dst string) error {
 	var err error

+ 3 - 1
pkg/utils/httper/httper.go

@@ -3,6 +3,7 @@ package httper
 import (
 	"bytes"
 	"encoding/json"
+	"fmt"
 	"io"
 	"io/ioutil"
 	"net/http"
@@ -67,7 +68,8 @@ func Post(url string, data []byte, contentType string, head map[string]string) (
 	client := &http.Client{Timeout: 5 * time.Second}
 	resp, error := client.Do(req)
 	if error != nil {
-		panic(error)
+		fmt.Println(error)
+		return
 	}
 	defer resp.Body.Close()
 

+ 0 - 1
pkg/utils/ini_helper.go

@@ -1 +0,0 @@
-package utils

+ 18 - 0
pkg/utils/oasis_err/e.go

@@ -37,9 +37,18 @@ const (
 	FILE_DOES_NOT_EXIST = 60001
 	FILE_READ_ERROR     = 60002
 	FILE_DELETE_ERROR   = 60003
+	DIR_NOT_EXISTS      = 60004
 
 	//shortcuts
 	SHORTCUTS_URL_ERROR = 70001
+
+	//person
+	PERSON_REMOTE_ERROR   = 80001
+	PERSON_DOWN_NOT_EXIST = 80002
+	PERSON_EXIST_DOWNLOAD = 80003
+	PERSON_NOT_EXIST_USER = 80004
+	PERSON_EXIST_FRIEND   = 80005
+	PERSON_MYSELF         = 80006
 )
 
 var MsgFlags = map[int]string{
@@ -78,9 +87,18 @@ var MsgFlags = map[int]string{
 	//
 	FILE_DOES_NOT_EXIST: "File does not exist",
 
+	DIR_NOT_EXISTS: "Directory does not exist",
+
 	FILE_READ_ERROR:     "File read error",
 	FILE_DELETE_ERROR:   "Delete error",
 	SHORTCUTS_URL_ERROR: "URL error",
+
+	PERSON_REMOTE_ERROR:   "Remote connection error",
+	PERSON_DOWN_NOT_EXIST: "Download record does not exist",
+	PERSON_EXIST_DOWNLOAD: "The same download task exists",
+	PERSON_EXIST_FRIEND:   "Friend already exist",
+	PERSON_NOT_EXIST_USER: "User does not exist",
+	PERSON_MYSELF:         "You can not add yourself",
 }
 
 //获取错误信息

+ 0 - 47
pkg/zerotier/zerotier_api.go

@@ -1,47 +0,0 @@
-package zerotier
-
-import (
-	httper2 "github.com/IceWhaleTech/CasaOS/pkg/utils/httper"
-	"github.com/tidwall/gjson"
-	"net/http"
-)
-
-func PostData(url, token string, data string) interface{} {
-
-	body, code := httper2.ZeroTierPostJson(url, data, GetHead(token))
-
-	if code != http.StatusOK {
-		return ""
-	}
-	result := gjson.Parse(body)
-	return result.Value()
-}
-
-func GetData(url, token string) interface{} {
-
-	body, code := httper2.ZeroTierGet(url, GetHead(token))
-
-	if code != http.StatusOK {
-		return ""
-	}
-	result := gjson.Parse(body)
-	return result.Value()
-}
-
-func DeleteMember(url, token string) interface{} {
-
-	body, code := httper2.ZeroTierDelete(url, GetHead(token))
-
-	if code != http.StatusOK {
-		return ""
-	}
-	result := gjson.Parse(body)
-	return result.Value()
-}
-
-func GetHead(token string) map[string]string {
-	var head = make(map[string]string)
-	head["Authorization"] = "Bearer " + token
-	head["Content-Type"] = "application/json"
-	return head
-}

+ 50 - 3
route/init.go

@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"encoding/xml"
 	"fmt"
+	"runtime"
 	"strconv"
 	"time"
 
@@ -25,7 +26,7 @@ func InitFunction() {
 	Update2_3()
 	CheckSerialDiskMount()
 
-	CheckToken2_9()
+	CheckToken2_11()
 
 }
 
@@ -231,16 +232,62 @@ func CheckSerialDiskMount() {
 	}
 	service.MyService.Disk().RemoveLSBLKCache()
 	command.OnlyExec("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;AutoRemoveUnuseDir")
-
 }
 func Update2_3() {
 	command.OnlyExec("source " + config.AppInfo.ProjectPath + "/shell/assist.sh")
+
 }
-func CheckToken2_9() {
+func CheckToken2_11() {
 	if len(config.ServerInfo.Token) == 0 {
 		token := uuid.NewV4().String
 		config.ServerInfo.Token = token()
 		config.Cfg.Section("server").Key("Token").SetValue(token())
 		config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
 	}
+	if len(config.AppInfo.RootPath) == 0 {
+		config.Cfg.Section("app").Key("RootPath").SetValue("/casaOS")
+		config.AppInfo.RootPath = "/casaOS"
+		config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
+	}
+	// if len(config.ServerInfo.Handshake) == 0 {
+	// 	config.Cfg.Section("app").Key("RootPath").SetValue("/casaOS")
+	// 	config.AppInfo.RootPath = "/casaOS"
+	// 	config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
+	// }
+	sysType := runtime.GOOS
+	if len(config.FileSettingInfo.DownloadDir) == 0 {
+		downloadPath := "/DATA/Downloads"
+		if sysType == "windows" {
+			downloadPath = "C:\\CasaOS\\DATA\\Downloads"
+		}
+		if sysType == "darwin" {
+			downloadPath = "~/CasaOS/DATA/Downloads"
+		}
+		config.Cfg.Section("file").Key("DownloadDir").SetValue(downloadPath)
+		config.FileSettingInfo.DownloadDir = downloadPath
+		file.IsNotExistMkDir(config.FileSettingInfo.DownloadDir)
+		config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
+	}
+
+	if len(config.UserInfo.Description) == 0 {
+		config.Cfg.Section("user").Key("Description").SetValue("nothing")
+		config.UserInfo.Description = "nothing"
+		config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
+	}
+	if len(config.ServerInfo.Handshake) == 0 {
+		config.Cfg.Section("server").Key("Handshake").SetValue("socket.casaos.io")
+		config.ServerInfo.Handshake = "socket.casaos.io"
+		config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
+	}
+
+	service.MyService.System().ExecUSBAutoMountShell(config.ServerInfo.USBAutoMount)
+
+	// str := []string{}
+	// str = append(str, "ddd")
+	// str = append(str, "aaa")
+	// ddd := strings.Join(str, "|")
+	// config.Cfg.Section("file").Key("ShareDir").SetValue(ddd)
+
+	// config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
+
 }

+ 33 - 59
route/route.go

@@ -18,6 +18,7 @@ var OnlineDemo bool = false
 func InitRouter() *gin.Engine {
 
 	r := gin.Default()
+
 	r.Use(middleware.Cors())
 	r.Use(gzip.Gzip(gzip.DefaultCompression))
 	gin.SetMode(config.ServerInfo.RunMode)
@@ -39,7 +40,7 @@ func InitRouter() *gin.Engine {
 	//set user
 	r.POST("/v1/user/setusernamepwd", v1.Set_Name_Pwd)
 	//get user info
-	r.GET("/v1/user/info", v1.UserInfo)
+	r.GET("/v1/user/info", v1.GetUserInfo)
 
 	v1Group := r.Group("/v1")
 
@@ -50,13 +51,18 @@ func InitRouter() *gin.Engine {
 		{
 
 			//chang head
-			v1UserGroup.POST("/changhead", v1.Up_Load_Head)
+			v1UserGroup.POST("/head", v1.PostUserHead)
 			//chang user name
-			v1UserGroup.PUT("/changusername", v1.Chang_User_Name)
+			v1UserGroup.PUT("/username", v1.PutUserName)
 			//chang pwd
-			v1UserGroup.PUT("/changuserpwd", v1.Chang_User_Pwd)
+			v1UserGroup.PUT("/password", v1.PutUserPwd)
 			//edit user info
-			v1UserGroup.POST("/changuserinfo", v1.Chang_User_Info)
+			v1UserGroup.POST("/info", v1.PostUserChangeInfo)
+			v1UserGroup.PUT("/nick", v1.PutUserChangeNick)
+			v1UserGroup.PUT("/desc", v1.PutUserChangeDesc)
+			v1UserGroup.POST("/person/info", v1.PostUserPersonInfo)
+
+			v1UserGroup.GET("/shareid", v1.GetUserShareID)
 
 		}
 
@@ -76,51 +82,6 @@ func InitRouter() *gin.Engine {
 			//获取系统信息
 			v1ZiMaGroup.GET("/sysinfo", v1.SysInfo)
 		}
-
-		v1ZeroTierGroup := v1Group.Group("/zerotier")
-		v1ZeroTierGroup.Use()
-		{
-			//获取zerotier token
-			v1ZeroTierGroup.POST("/login", v1.ZeroTierGetToken)
-			//注册zerotier
-			v1ZeroTierGroup.POST("/register", v1.ZeroTierRegister)
-			//是否需要登录
-			v1ZeroTierGroup.GET("/islogin", v1.ZeroTierIsNeedLogin)
-			//获取网络列表
-			v1ZeroTierGroup.GET("/list", v1.ZeroTierGetNetworkList)
-			//加入网络
-			v1ZeroTierGroup.POST("/join/:id", v1.ZeroTierJoinNetwork)
-			//离开网络
-			v1ZeroTierGroup.POST("/leave/:id", v1.ZeroTierLeaveNetwork)
-			//详情
-			v1ZeroTierGroup.GET("/info/:id", v1.ZeroTierGetNetworkGetInfo)
-			////网络状态
-			//v1ZeroTierGroup.GET("/status", v1.ZeroTierGetNetworkGetStatus)
-			//修改网络类型
-			//v1ZeroTierGroup.PUT("/type/:id", v1.ZeroTierEditType)
-			//修改网络类型
-			//v1ZeroTierGroup.PUT("/name/:id", v1.ZeroTierEditName)
-			//修改v6 assign
-			//v1ZeroTierGroup.PUT("/v6assign/:id", v1.ZeroTierEditV6Assign)
-			//修改 broadcast
-			//v1ZeroTierGroup.PUT("/broadcast/:id", v1.ZeroTierEditBroadcast)
-			//create new network
-			v1ZeroTierGroup.POST("/create", v1.ZeroTierCreateNetwork)
-			//获取用户列表
-			v1ZeroTierGroup.GET("/member/:id", v1.ZeroTierMemberList)
-			//修改用户信息
-			//v1ZeroTierGroup.PUT("/members/:id/auth/:mId", v1.ZeroTierMemberAuth)
-			//修改网络用户name
-			//v1ZeroTierGroup.PUT("/members/:id/name/:mId", v1.ZeroTierMemberName)
-			v1ZeroTierGroup.DELETE("/members/:id/del/:mId", v1.ZeroTierMemberDelete)
-			v1ZeroTierGroup.DELETE("/network/:id/del", v1.ZeroTierDeleteNetwork)
-			//修改网络用户bridge功能
-			//v1ZeroTierGroup.PUT("/members/:id/bridge/:mId", v1.ZeroTierMemberBridge)
-			v1ZeroTierGroup.PUT("/edit/:id", v1.ZeroTierEdit)
-			v1ZeroTierGroup.GET("/joined/list", v1.ZeroTierJoinedList)
-			v1ZeroTierGroup.PUT("/member/:id/edit/:mId", v1.ZeroTierMemberEdit)
-
-		}
 		v1DDNSGroup := v1Group.Group("/ddns")
 		v1DDNSGroup.Use()
 		{
@@ -199,6 +160,9 @@ func InitRouter() *gin.Engine {
 			v1SysGroup.PUT("/port", v1.PutCasaOSPort)
 			v1SysGroup.POST("/kill", v1.PostKillCasaOS)
 			v1SysGroup.GET("/info", v1.Info)
+			v1SysGroup.PUT("/usb/off", v1.PutSystemOffUSBAutoMount)
+			v1SysGroup.GET("/usb/on", v1.PutSystemOnUSBAutoMount)
+			v1SysGroup.GET("/usb", v1.GetSystemUSBAutoMount)
 		}
 		v1FileGroup := v1Group.Group("/file")
 		v1FileGroup.Use()
@@ -214,6 +178,7 @@ func InitRouter() *gin.Engine {
 			v1FileGroup.POST("/create", v1.PostCreateFile)
 
 			v1FileGroup.GET("/download", v1.GetDownloadFile)
+			v1FileGroup.GET("/new/download", v1.GetFileDownloadNew)
 			v1FileGroup.POST("/operate", v1.PostOperateFileOrDir)
 			v1FileGroup.DELETE("/delete", v1.DeleteFile)
 			v1FileGroup.PUT("/update", v1.PutFileContent)
@@ -287,17 +252,26 @@ func InitRouter() *gin.Engine {
 		{
 			v1SearchGroup.GET("/search", v1.GetSearchList)
 		}
-		v1PersonGroup := v1Group.Group("/persion")
+		v1PersonGroup := v1Group.Group("/person")
 		v1PersonGroup.Use()
 		{
-			// v1PersonGroup.GET("/test", v1.PersonTest)
-			// v1PersonGroup.GET("/users", v1.Users)                        //用户列表
-			// v1PersonGroup.POST("/user", v1.Users)                        //添加用户
-			// v1PersonGroup.GET("/directory", v1.Users)                    //文件列表
-			v1PersonGroup.GET("/download", v1.GetPersionFile) //下载文件
-			// v1PersonGroup.PUT("/edit/:id", v1.EditUser)                  //修改好友
-			v1PersonGroup.GET("/list", v1.GetPersionDownloadList) //下载列表(需要考虑试试下载速度)
-			// v1PersonGroup.PUT("/state/:id", v1.PutPersionCancelDownload) //修改下载状态(开始暂停删除)
+			v1PersonGroup.GET("/test", v1.PersonTest)
+			v1PersonGroup.GET("/users", v1.GetPersonFriend)
+			v1PersonGroup.POST("/user/:shareids", v1.PostAddPersonFriend)
+			v1PersonGroup.DELETE("/user/:shareid", v1.DeletePersonFriend)
+			v1PersonGroup.GET("/directory", v1.GetPersonDirectory)
+			v1PersonGroup.GET("/file", v1.GetPersonFile)
+			v1PersonGroup.GET("/refile/:uuid", v1.GetPersonReFile)
+			v1PersonGroup.PUT("/remarks/:shareid", v1.PutPersonRemarks)
+			v1PersonGroup.GET("/list", v1.GetPersonDownloadList)
+			v1PersonGroup.DELETE("/file/:uuid", v1.DeletePersonDownloadFile)
+
+			v1PersonGroup.POST("/share", v1.PostPersonShare)
+			v1PersonGroup.GET("/share", v1.GetPersonShare)
+			v1PersonGroup.POST("/down/dir", v1.PostPersonDownDir)
+			v1PersonGroup.GET("/down/dir", v1.GetPersonDownDir)
+			v1PersonGroup.PUT("/block/:shareid", v1.PutPersonBlock)
+			v1PersonGroup.GET("/public", v1.GetPersonPublic)
 
 		}
 		v1AnalyseGroup := v1Group.Group("/analyse")

+ 1 - 1
route/v1/app.go

@@ -248,7 +248,7 @@ func ShareAppFile(c *gin.Context) {
 // @Tags app
 // @Security ApiKeyAuth
 // @Success 200 {string} string "ok"
-// @Router /app/share [post]
+// @Router /app/shares [post]
 func AppListResourceUsage() {
 
 }

+ 49 - 4
route/v1/file.go

@@ -7,6 +7,7 @@ import (
 	"io"
 	"io/ioutil"
 	"net/http"
+	url2 "net/url"
 	"os"
 	"path"
 	"strconv"
@@ -17,6 +18,7 @@ import (
 	oasis_err2 "github.com/IceWhaleTech/CasaOS/pkg/utils/oasis_err"
 	"github.com/IceWhaleTech/CasaOS/service"
 	"github.com/gin-gonic/gin"
+	"github.com/spf13/afero"
 )
 
 func downloadReadFile(c *gin.Context) {
@@ -157,16 +159,59 @@ func GetDownloadFile(c *gin.Context) {
 	//获取文件的名称
 	fileName := path.Base(filePath)
 	c.Header("Content-Type", "application/octet-stream")
-	c.Header("Content-Disposition", "attachment; filename="+fileName)
+	c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url2.PathEscape(fileName))
 	c.Header("Content-Transfer-Encoding", "binary")
 	c.Header("Cache-Control", "no-cache")
-	c.Header("Content-Type", "application/octet-stream")
-	c.Header("Content-Disposition", "attachment; filename="+fileName)
-	c.Header("Content-Transfer-Encoding", "binary")
 
 	c.File(filePath)
 }
 
+// @Summary download
+// @Produce  application/json
+// @Accept application/json
+// @Tags file
+// @Security ApiKeyAuth
+// @Param path query string true "path of file"
+// @Success 200 {string} string "ok"
+// @Router /file/new/download [get]
+func GetFileDownloadNew(c *gin.Context) {
+	filePath := c.Query("path")
+	if len(filePath) == 0 {
+		c.JSON(http.StatusOK, model.Result{
+			Success: oasis_err2.INVALID_PARAMS,
+			Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS),
+		})
+		return
+	}
+	if !file.Exists(filePath) {
+		c.JSON(http.StatusOK, model.Result{
+			Success: oasis_err2.FILE_DOES_NOT_EXIST,
+			Message: oasis_err2.GetMsg(oasis_err2.FILE_DOES_NOT_EXIST),
+		})
+		return
+	}
+	//打开文件
+	fileStat, _ := os.Stat(filePath)
+	var AppFs = afero.NewOsFs()
+	fileT, _ := AppFs.Open(filePath)
+	//fileTmp, _ := os.Open(filePath)
+	//defer fileTmp.Close()
+	//获取文件的名称
+	//fileName := path.Base(filePath)
+
+	//c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url2.PathEscape(fileName))
+	//在线
+	//c.Header("Content-Disposition", "inline")
+	// extraHeaders := map[string]string{
+	// 	"Content-Disposition": `attachment; filename="` + url2.PathEscape(fileName) + `"`,
+	// }
+
+	//c.Header("Cache-Control", "private")
+	//c.Header("Content-Type", "application/octet-stream")
+
+	http.ServeContent(c.Writer, c.Request, fileStat.Name(), fileStat.ModTime(), fileT)
+}
+
 // @Summary 获取目录列表
 // @Produce  application/json
 // @Accept application/json

+ 444 - 33
route/v1/persion.go

@@ -2,84 +2,495 @@ package v1
 
 import (
 	"encoding/json"
+	"fmt"
+	"io/ioutil"
 	"net/http"
+	"reflect"
+	"strconv"
+	"strings"
 	"time"
 
 	"github.com/IceWhaleTech/CasaOS/model"
 	"github.com/IceWhaleTech/CasaOS/pkg/config"
+	"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
 	oasis_err2 "github.com/IceWhaleTech/CasaOS/pkg/utils/oasis_err"
 	"github.com/IceWhaleTech/CasaOS/service"
 	model2 "github.com/IceWhaleTech/CasaOS/service/model"
 	"github.com/IceWhaleTech/CasaOS/types"
 	"github.com/gin-gonic/gin"
-	"github.com/gorilla/websocket"
 	uuid "github.com/satori/go.uuid"
 )
 
 func PersonTest(c *gin.Context) {
+	token := c.Query("token")
+	_, err := uuid.FromString(token)
+	fmt.Println(err)
 
 	//service.MyService.Person().GetPersionInfo("fb2333a1-72b2-4cb4-9e31-61ccaffa55b9")
 
-	m := model.ConnectState{}
-	m.CreatedAt = time.Now()
-	m.From = config.ServerInfo.Token
-	m.To = "fb2333a1-72b2-4cb4-9e31-61ccaffa55b9"
-	m.Type = ""
-	m.UUId = uuid.NewV4().String()
-
-	//service.MyService.Person().Handshake(m)
 	msg := model.MessageModel{}
-	msg.Type = "connection"
-	msg.Data = "fb2333a1-72b2-4cb4-9e31-61ccaffa55b9"
+	msg.Type = types.PERSONHELLO
+	msg.Data = ""
 	msg.From = config.ServerInfo.Token
-	msg.UUId = "1234567890"
-	b, _ := json.Marshal(msg)
-	err := service.WebSocketConn.WriteMessage(websocket.TextMessage, b)
+	msg.To = token
+	msg.UUId = uuid.NewV4().String()
+
+	dd, err := service.Dial(msg, true)
 	if err == nil {
+		fmt.Println(err)
+	}
+	fmt.Println(dd)
+	user := service.MyService.Casa().GetUserInfoByShareId(token)
+	if reflect.DeepEqual(user, model.UserInfo{}) {
+		fmt.Println("空数据")
+	}
+	fmt.Println(user)
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)})
+}
+
+// @Summary Retry the file that failed to download
+// @Produce  application/json
+// @Accept application/json
+// @Tags person
+// @Param  uui path string true "download uuid"
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /person/refile/{uuid} [get]
+func GetPersonReFile(c *gin.Context) {
+
+	uid := c.Param("uuid")
+	_, err := uuid.FromString(uid)
+	if err != nil {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
+		return
+	}
+
+	task := service.MyService.Download().GetDownloadById(uid)
+	if reflect.DeepEqual(task, model2.PersonDownloadDBModel{}) {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.PERSON_REMOTE_ERROR, Message: oasis_err2.GetMsg(oasis_err2.PERSON_REMOTE_ERROR)})
+		return
+	}
+	token := task.From
+	if _, ok := service.UDPAddressMap[token]; !ok {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.PERSON_REMOTE_ERROR, Message: oasis_err2.GetMsg(oasis_err2.PERSON_REMOTE_ERROR)})
 		return
 	}
+
+	m := model.MessageModel{}
+	m.Data = task.Path
+	m.From = config.ServerInfo.Token
+	m.To = token
+	m.Type = types.PERSONDOWNLOAD
+	m.UUId = uid
+	go service.Dial(m, false)
+
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)})
 }
 
-//get other persion file
-func GetPersionFile(c *gin.Context) {
+// @Summary download file
+// @Produce  application/json
+// @Accept application/json
+// @Tags person
+// @Param  share_id query string true "opponent share_id"
+// @Param  path query string true "file path"
+// @Param  file_name query string true "file name"
+// @Param  local_path query string true "local_path"
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /person/file [get]
+func GetPersonFile(c *gin.Context) {
+
 	path := c.Query("path")
-	persion := c.Query("persion")
-	if len(path) == 0 && len(persion) == 0 {
+	localPath := c.Query("local_path")
+	token := c.Query("share_id")
+	fileName := c.Query("file_name")
+	_, err := uuid.FromString(token)
+	if len(path) == 0 || err != nil || len(localPath) == 0 || len(fileName) == 0 {
 		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
 		return
 	}
-	//任务标识
-	uuid := uuid.NewV4().String()
+	if file.CheckNotExist(localPath) {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.DIR_NOT_EXISTS, Message: oasis_err2.GetMsg(oasis_err2.DIR_NOT_EXISTS)})
+		return
+	}
+	if _, ok := service.UDPAddressMap[token]; !ok {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.PERSON_REMOTE_ERROR, Message: oasis_err2.GetMsg(oasis_err2.PERSON_REMOTE_ERROR)})
+		return
+	}
 
-	//1.通知对方需要下载
-	service.MyService.Person().GetFileDetail(uuid, path, persion)
+	if _, ok := service.UDPAddressMap[token]; !ok {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.PERSON_REMOTE_ERROR, Message: oasis_err2.GetMsg(oasis_err2.PERSON_REMOTE_ERROR)})
+		return
+	}
 
-	//2.添加数据库
+	// task id
+	uuid := uuid.NewV4().String()
 
-	task := model2.PersionDownloadDBModel{}
+	task := model2.PersonDownloadDBModel{}
 	task.UUID = uuid
-	task.Name = ""
+	task.Name = fileName
 	task.Length = 0
+	task.From = token
+	task.Path = path
 	task.Size = 0
 	task.State = types.DOWNLOADAWAIT
-	task.TempPath = ""
+	task.Created = time.Now().Unix()
 	task.Type = 0
-	service.MyService.Person().AddDownloadTask(task)
+	task.LocalPath = localPath
+	if service.MyService.Download().GetDownloadListByPath(task) > 0 {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.PERSON_EXIST_DOWNLOAD, Message: oasis_err2.GetMsg(oasis_err2.PERSON_EXIST_DOWNLOAD)})
+		return
+	}
+	service.MyService.Download().AddDownloadTask(task)
+
+	m := model.MessageModel{}
+	m.Data = path
+	m.From = config.ServerInfo.Token
+	m.To = token
+	m.Type = types.PERSONDOWNLOAD
+	m.UUId = uuid
+	go service.Dial(m, false)
+
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)})
+}
+
+// @Summary delete download file records
+// @Produce  application/json
+// @Accept application/json
+// @Tags person
+// @Param  uuid path string true "download uuid"
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /person/file/{uuid} [delete]
+func DeletePersonDownloadFile(c *gin.Context) {
+
+	id := c.Param("uuid")
+	_, err := uuid.FromString(id)
+	if err != nil {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
+		return
+	}
+
+	task := service.MyService.Download().GetDownloadById(id)
+	if task.State == types.DOWNLOADING {
+		m := model.MessageModel{}
+		m.Data = ""
+		m.From = config.ServerInfo.Token
+		m.To = task.From
+		m.Type = types.PERSONCANCEL
+		m.UUId = task.UUID
+		service.CancelList[task.UUID] = task.UUID
+		service.Dial(m, false)
+	}
+	service.MyService.Download().DelDownload(id)
+
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)})
+}
+
+// @Summary Get file download list
+// @Produce  application/json
+// @Accept application/json
+// @Tags person
+// @Param  state query int false "wait:0,downloading:1,pause:2,finish:3,error:4,finished:5" Enums(0,1,2,3,4,5)
+// @Security ApiKeyAuth
+// @Success 200 {object} []model2.PersonDownloadDBModel
+// @Router /person/list [get]
+func GetPersonDownloadList(c *gin.Context) {
+	state := c.DefaultQuery("state", "")
+	list := service.MyService.Download().GetDownloadListByState(state)
+	//if it is  downloading, it need to add 'already'
+	for i := 0; i < len(list); i++ {
+		if list[i].State == types.DOWNLOADING {
+			tempDir := config.AppInfo.RootPath + "/temp" + "/" + list[i].UUID
+			files, err := ioutil.ReadDir(tempDir)
+			if err == nil {
+				list[i].Already = len(files)
+			}
+		}
+		list[i].Duration = time.Now().Unix() - list[i].Created
+	}
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: list})
+}
+
+// @Summary edit friend's remarks
+// @Produce  application/json
+// @Accept application/json
+// @Tags person
+// @Param remarks formData string true "remarks name"
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /person/remarks/{shareid} [put]
+func PutPersonRemarks(c *gin.Context) {
+	token := c.Param("shareid")
+	_, err := uuid.FromString(token)
+	mark := c.PostForm("remarks")
+	if err != nil || len(mark) == 0 {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
+		return
+	}
+	friend := model2.FriendModel{}
+	friend.Token = token
+	friend.Mark = mark
+	service.MyService.Friend().EditFriendMark(friend)
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)})
+}
+
+// @Summary get my friend list
+// @Produce  application/json
+// @Accept application/json
+// @Tags person
+// @Security ApiKeyAuth
+// @Success 200 {object}  []model2.FriendModel
+// @Router /person/users [get]
+func GetPersonFriend(c *gin.Context) {
+	list := service.MyService.Friend().GetFriendList()
+	for i := 0; i < len(list); i++ {
+		if v, ok := service.UDPAddressMap[list[i].Token]; ok && len(v) > 0 {
+			list[i].OnLine = true
+		}
+	}
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: list})
+}
+
+// @Summary add friend
+// @Produce  application/json
+// @Accept application/json
+// @Tags person
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /person/user/{shareids} [post]
+func PostAddPersonFriend(c *gin.Context) {
+	token := c.Param("shareids")
+	tokenList := strings.Split(token, ",")
+
+	for _, v := range tokenList {
+		_, err := uuid.FromString(v)
+		if err != nil {
+			c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
+			return
+		}
+
+		if v == config.ServerInfo.Token {
+			c.JSON(http.StatusOK, model.Result{Success: oasis_err2.PERSON_MYSELF, Message: oasis_err2.GetMsg(oasis_err2.PERSON_MYSELF)})
+			return
+		}
+
+		udb := service.MyService.Friend().GetFriendById(model2.FriendModel{Token: v})
+		if !reflect.DeepEqual(udb, model2.FriendModel{Token: v}) {
+			c.JSON(http.StatusOK, model.Result{Success: oasis_err2.PERSON_EXIST_FRIEND, Message: oasis_err2.GetMsg(oasis_err2.PERSON_EXIST_FRIEND)})
+			return
+		}
+
+		user := service.MyService.Casa().GetUserInfoByShareId(v)
+		if reflect.DeepEqual(user, model.UserInfo{}) {
+			c.JSON(http.StatusOK, model.Result{Success: oasis_err2.PERSON_NOT_EXIST_USER, Message: oasis_err2.GetMsg(oasis_err2.PERSON_NOT_EXIST_USER)})
+			return
+		}
+
+		message := model.MessageModel{}
+		message.Type = types.PERSONCONNECTION
+		message.Data = v
+		message.From = config.ServerInfo.Token
+		message.To = v
+		message.UUId = uuid.NewV4().String()
+
+		go service.Dial(message, true)
+
+		friend := model2.FriendModel{}
+		friend.Token = v
+		friend.Avatar = user.Avatar
+		friend.Block = false
+		friend.NickName = user.NickName
+		friend.Profile = user.Desc
+		friend.Version = user.Version
+		service.MyService.Friend().AddFriend(friend)
+	}
 
 	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)})
 }
-func GetPersionDownloadList(c *gin.Context) {
+
+// @Summary Get a list of directories
+// @Produce  application/json
+// @Accept application/json
+// @Tags person
+// @Param  share_id query string true "Opponent share_id"
+// @Param  path query string true "dir path"
+// @Security ApiKeyAuth
+// @Success 200 {object}  []model.Path
+// @Router /person/directory [get]
+func GetPersonDirectory(c *gin.Context) {
 	path := c.Query("path")
-	persion := c.Query("persion")
-	if len(path) == 0 && len(persion) == 0 {
+	token := c.Query("share_id")
+	_, err := uuid.FromString(token)
+	if len(path) == 0 || err != nil {
 		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
 		return
 	}
-	//任务标识
+	if _, ok := service.UDPAddressMap[token]; !ok {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.PERSON_REMOTE_ERROR, Message: oasis_err2.GetMsg(oasis_err2.PERSON_REMOTE_ERROR)})
+		return
+	}
 	uuid := uuid.NewV4().String()
+	m := model.MessageModel{}
+	m.Data = path
+	m.From = config.ServerInfo.Token
+	m.To = token
+	m.Type = types.PERSONDIRECTORY
+	m.UUId = uuid
+	result, err := service.Dial(m, false)
+	if err != nil {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.ERROR, Message: oasis_err2.GetMsg(oasis_err2.ERROR), Data: err.Error()})
+		return
+	}
+	dataModel := []model.Path{}
+	if uuid == m.UUId {
+		dataModelByte, _ := json.Marshal(result.Data)
+		err := json.Unmarshal(dataModelByte, &dataModel)
+		if err != nil {
+			c.JSON(http.StatusOK, model.Result{Success: oasis_err2.ERROR, Message: oasis_err2.GetMsg(oasis_err2.ERROR), Data: err.Error()})
+			return
+		}
+	}
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: dataModel})
+}
 
-	//1.通知对方需要下载
-	service.MyService.Person().GetFileDetail(uuid, path, persion)
+// @Summary Modify the download storage directory
+// @Produce  application/json
+// @Accept  multipart/form-data
+// @Tags person
+// @Security ApiKeyAuth
+// @Param path formData string true "path"
+// @Success 200 {string} string "ok"
+// @Router /person/down/dir [post]
+func PostPersonDownDir(c *gin.Context) {
 
+	downPath := c.PostForm("path")
+
+	if len(downPath) == 0 {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
+		return
+	}
+	if file.CheckNotExist(downPath) {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.DIR_NOT_EXISTS, Message: oasis_err2.GetMsg(oasis_err2.DIR_NOT_EXISTS)})
+		return
+	}
+	config.Cfg.Section("file").Key("DownloadDir").SetValue(downPath)
+	config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
+	config.FileSettingInfo.DownloadDir = downPath
 	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)})
 }
+
+// @Summary Get the download storage directory
+// @Produce  application/json
+// @Accept application/json
+// @Tags person
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /person/down/dir [get]
+func GetPersonDownDir(c *gin.Context) {
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: config.FileSettingInfo.DownloadDir})
+}
+
+// @Summary Modify the shared directory
+// @Produce  application/json
+// @Accept  multipart/form-data
+// @Tags person
+// @Security ApiKeyAuth
+// @Param share formData string true "share"
+// @Success 200 {string} string "ok"
+// @Router /person/share [post]
+func PostPersonShare(c *gin.Context) {
+
+	share := c.PostForm("share")
+
+	if len(share) == 0 {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
+		return
+	}
+
+	var list []string
+	json.Unmarshal([]byte(share), &list)
+
+	if len(list) == 0 {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
+		return
+	}
+	for _, v := range list {
+		if !file.Exists(v) {
+			c.JSON(http.StatusOK, model.Result{Success: oasis_err2.FILE_ALREADY_EXISTS, Message: oasis_err2.GetMsg(oasis_err2.FILE_ALREADY_EXISTS)})
+			return
+		}
+	}
+
+	config.Cfg.Section("file").Key("ShareDir").SetValue(strings.Join(list, "|"))
+	config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
+	config.FileSettingInfo.ShareDir = list
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)})
+}
+
+// @Summary Get the shared directory
+// @Produce  application/json
+// @Accept application/json
+// @Tags person
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /person/share [get]
+func GetPersonShare(c *gin.Context) {
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: config.FileSettingInfo.ShareDir})
+}
+
+// @Summary Modify disabled status
+// @Produce  application/json
+// @Accept application/json
+// @Tags person
+// @Param block formData bool false "Disable or not,Default:false "
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /person/block/{shareid} [put]
+func PutPersonBlock(c *gin.Context) {
+	token := c.Param("shareid")
+	_, err := uuid.FromString(token)
+	block, _ := strconv.ParseBool(c.PostForm("block"))
+	if err != nil {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
+		return
+	}
+	friend := model2.FriendModel{}
+	friend.Token = token
+	friend.Block = block
+	service.MyService.Friend().EditFriendBlock(friend)
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)})
+}
+
+// @Summary Delete my friend
+// @Produce  application/json
+// @Accept application/json
+// @Tags person
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /person/user/{shareid} [delete]
+func DeletePersonFriend(c *gin.Context) {
+	token := c.Param("shareid")
+	_, err := uuid.FromString(token)
+	if err != nil {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
+		return
+	}
+	friend := model2.FriendModel{}
+	friend.Token = token
+
+	service.MyService.Friend().DeleteFriend(friend)
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)})
+}
+
+// @Summary Get public person
+// @Produce  application/json
+// @Accept application/json
+// @Tags person
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /person/public [delete]
+func GetPersonPublic(c *gin.Context) {
+	list := service.MyService.Casa().GetPersonPublic()
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: list})
+}

+ 55 - 1
route/v1/system.go

@@ -251,6 +251,60 @@ func PostKillCasaOS(c *gin.Context) {
 	os.Exit(0)
 }
 
+// @Summary Turn off usb auto-mount
+// @Produce  application/json
+// @Accept application/json
+// @Tags sys
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /sys/usg/off [put]
+func PutSystemOffUSBAutoMount(c *gin.Context) {
+	service.MyService.System().UpdateUSBAutoMount("False")
+	service.MyService.System().ExecUSBAutoMountShell("False")
+	c.JSON(http.StatusOK,
+		model.Result{
+			Success: oasis_err.SUCCESS,
+			Message: oasis_err.GetMsg(oasis_err.SUCCESS),
+		})
+}
+
+// @Summary Turn off usb auto-mount
+// @Produce  application/json
+// @Accept application/json
+// @Tags sys
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /sys/usb [get]
+func GetSystemUSBAutoMount(c *gin.Context) {
+	state := "True"
+	if config.ServerInfo.USBAutoMount == "False" {
+		state = "False"
+	}
+	c.JSON(http.StatusOK,
+		model.Result{
+			Success: oasis_err.SUCCESS,
+			Message: oasis_err.GetMsg(oasis_err.SUCCESS),
+			Data:    state,
+		})
+}
+
+// @Summary Turn off usb auto-mount
+// @Produce  application/json
+// @Accept application/json
+// @Tags sys
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /sys/usb/on [put]
+func PutSystemOnUSBAutoMount(c *gin.Context) {
+	service.MyService.System().UpdateUSBAutoMount("True")
+	service.MyService.System().ExecUSBAutoMountShell("True")
+	c.JSON(http.StatusOK,
+		model.Result{
+			Success: oasis_err.SUCCESS,
+			Message: oasis_err.GetMsg(oasis_err.SUCCESS),
+		})
+}
+
 // @Summary system info
 // @Produce  application/json
 // @Accept application/json
@@ -374,7 +428,7 @@ func Info(c *gin.Context) {
 			if n.Name == netCardName {
 				item := *(*model.IOCountersStat)(unsafe.Pointer(&n))
 				item.State = strings.TrimSpace(service.MyService.ZiMa().GetNetState(n.Name))
-				item.DateTime = time.Now()
+				item.Time = time.Now().Unix()
 				newNet = append(newNet, item)
 				break
 			}

+ 107 - 23
route/v1/user.go

@@ -40,7 +40,7 @@ func Set_Name_Pwd(c *gin.Context) {
 		return
 	}
 	//开始设置
-	err := user_service.SetUser(username, pwd, "", "", "")
+	err := user_service.SetUser(username, pwd, "", "", "", "")
 	if err != nil {
 		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: fmt.Sprintf("%v", err)})
 		return
@@ -102,7 +102,7 @@ func Login(c *gin.Context) {
 // @Security ApiKeyAuth
 // @Success 200 {string} string "ok"
 // @Router /user/changhead [post]
-func Up_Load_Head(c *gin.Context) {
+func PostUserHead(c *gin.Context) {
 	file, _, _ := c.Request.FormFile("file")
 	user_service.UpLoadFile(file, config.UserInfo.Head)
 	c.JSON(http.StatusOK,
@@ -121,8 +121,8 @@ func Up_Load_Head(c *gin.Context) {
 // @Param oldname  formData string true "Old user name"
 // @Security ApiKeyAuth
 // @Success 200 {string} string "ok"
-// @Router /user/changusername [put]
-func Chang_User_Name(c *gin.Context) {
+// @Router /user/username [put]
+func PutUserName(c *gin.Context) {
 	if config.ServerInfo.LockAccount {
 		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.ACCOUNT_LOCK, Message: oasis_err2.GetMsg(oasis_err2.ACCOUNT_LOCK)})
 		return
@@ -133,7 +133,7 @@ func Chang_User_Name(c *gin.Context) {
 		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.ERROR, Message: oasis_err2.GetMsg(oasis_err2.ERROR)})
 		return
 	}
-	user_service.SetUser(username, "", "", "", "")
+	user_service.SetUser(username, "", "", "", "", "")
 	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)})
 }
 
@@ -142,14 +142,14 @@ func Chang_User_Name(c *gin.Context) {
 // @Accept multipart/form-data
 // @Tags user
 // @Param pwd formData string true "Password"
-// @Param oldpwd  formData string true "Old password"
+// @Param old_pwd  formData string true "Old password"
 // @Security ApiKeyAuth
 // @Success 200 {string} string "ok"
-// @Router /user/changuserpwd [put]
-func Chang_User_Pwd(c *gin.Context) {
-	oldpwd := c.PostForm("oldpwd")
+// @Router /user/password [put]
+func PutUserPwd(c *gin.Context) {
+	oldPwd := c.PostForm("old_pwd")
 	pwd := c.PostForm("pwd")
-	if config.UserInfo.PWD != oldpwd {
+	if config.UserInfo.PWD != oldPwd {
 		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.PWD_INVALID_OLD, Message: oasis_err2.GetMsg(oasis_err2.PWD_INVALID_OLD)})
 		return
 	}
@@ -161,53 +161,126 @@ func Chang_User_Pwd(c *gin.Context) {
 		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.PWD_IS_EMPTY, Message: oasis_err2.GetMsg(oasis_err2.PWD_IS_EMPTY)})
 		return
 	}
-	user_service.SetUser("", pwd, "", "", "")
+	user_service.SetUser("", pwd, "", "", "", "")
 	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)})
 }
 
-// @Summary 修改用户信息
+// @Summary edit user info
 // @Produce  application/json
 // @Accept multipart/form-data
 // @Tags user
-// @Param username formData string false "User Name"
+// @Param user_name formData string false "User Name"
 // @Param email formData string false "Email"
 // @Param description formData string false "Description"
 // @Param pwd formData string false "Password"
-// @Param oldpwd  formData string false "Old password"
+// @Param old_pwd  formData string false "Old password"
+// @Param nick_name formData string false "nick name"
 // @Security ApiKeyAuth
 // @Success 200 {string} string "ok"
-// @Router /user/changuserinfo [post]
-func Chang_User_Info(c *gin.Context) {
-	username := c.PostForm("username")
+// @Router /user/info [post]
+func PostUserChangeInfo(c *gin.Context) {
+	username := c.PostForm("user_name")
 	email := c.PostForm("email")
 	description := c.PostForm("description")
-	oldpwd := c.PostForm("oldpwd")
+	nickName := c.PostForm("nick_name")
+	oldpwd := c.PostForm("old_pwd")
 	pwd := c.PostForm("pwd")
 	if len(pwd) > 0 && config.UserInfo.PWD != oldpwd {
 		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.PWD_INVALID, Message: oasis_err2.GetMsg(oasis_err2.PWD_INVALID)})
 		return
 	}
-	user_service.SetUser(username, pwd, "", email, description)
-	data := make(map[string]string, 2)
+	user_service.SetUser(username, pwd, "", email, description, nickName)
+	data := make(map[string]string, 4)
 
 	data["token"] = jwt2.GetToken(username, pwd)
 	data["user_name"] = username
 	data["head"] = config.UserInfo.Head
+	data["nick_name"] = config.UserInfo.NickName
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: data})
+}
+
+// @Summary edit user nick
+// @Produce  application/json
+// @Accept multipart/form-data
+// @Tags user
+// @Param nick_name formData string false "nick name"
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /user/nick [put]
+func PutUserChangeNick(c *gin.Context) {
+
+	nickName := c.PostForm("nick_name")
+
+	if len(nickName) == 0 {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
+		return
+	}
+	user_service.SetUser("", "", "", "", "", nickName)
+	data := make(map[string]string, 1)
+	data["nick_name"] = config.UserInfo.NickName
+	go service.MyService.Casa().PushUserInfo()
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: data})
+}
+
+// @Summary edit user description
+// @Produce  application/json
+// @Accept multipart/form-data
+// @Tags user
+// @Param description formData string false "Description"
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /user/desc [put]
+func PutUserChangeDesc(c *gin.Context) {
+	desc := c.PostForm("description")
+
+	if len(desc) == 0 {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
+		return
+	}
+	user_service.SetUser("", "", "", "", desc, "")
+	data := make(map[string]string, 1)
+	data["description"] = config.UserInfo.Description
+	go service.MyService.Casa().PushUserInfo()
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: data})
+}
+
+// @Summary Modify user person information (Initialization use)
+// @Produce  application/json
+// @Accept multipart/form-data
+// @Tags user
+// @Param nick_name formData string false "user nick name"
+// @Param description formData string false "Description"
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /user/person/info [post]
+func PostUserPersonInfo(c *gin.Context) {
+	desc := c.PostForm("description")
+	nickName := c.PostForm("nick_name")
+	if len(desc) == 0 || len(nickName) == 0 {
+		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
+		return
+	}
+	user_service.SetUser("", "", "", "", desc, nickName)
+	data := make(map[string]string, 2)
+	data["description"] = config.UserInfo.Description
+	data["nick_name"] = config.UserInfo.NickName
+	go service.MyService.Casa().PushUserInfo()
 	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: data})
 }
 
-// @Summary 获取用户详情
+// @Summary get user info
 // @Produce  application/json
 // @Accept mapplication/json
 // @Tags user
 // @Success 200 {string} string "ok"
 // @Router /user/info [get]
-func UserInfo(c *gin.Context) {
-	var u = make(map[string]string, 2)
+func GetUserInfo(c *gin.Context) {
+	var u = make(map[string]string, 5)
 	u["user_name"] = config.UserInfo.UserName
 	u["head"] = config.UserInfo.Head
 	u["email"] = config.UserInfo.Email
 	u["description"] = config.UserInfo.Description
+	u["nick_name"] = config.UserInfo.NickName
 	c.JSON(http.StatusOK,
 		model.Result{
 			Success: oasis_err2.SUCCESS,
@@ -215,3 +288,14 @@ func UserInfo(c *gin.Context) {
 			Data:    u,
 		})
 }
+
+// @Summary Get my shareId
+// @Produce  application/json
+// @Accept application/json
+// @Tags user
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /user/shareid [get]
+func GetUserShareID(c *gin.Context) {
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: config.ServerInfo.Token})
+}

+ 0 - 475
route/v1/zerotier.go

@@ -1,475 +0,0 @@
-package v1
-
-import (
-	json2 "encoding/json"
-	"net/http"
-
-	"github.com/IceWhaleTech/CasaOS/model"
-	"github.com/IceWhaleTech/CasaOS/pkg/config"
-	oasis_err2 "github.com/IceWhaleTech/CasaOS/pkg/utils/oasis_err"
-	"github.com/IceWhaleTech/CasaOS/service"
-	"github.com/gin-gonic/gin"
-)
-
-// @Summary 登录zerotier获取token
-// @Produce  application/json
-// @Accept multipart/form-data
-// @Tags zerotier
-// @Param username formData string true "User name"
-// @Param pwd  formData string true "password"
-// @Security ApiKeyAuth
-// @Success 200 {string} string "ok"
-// @Router /zerotier/login [post]
-func ZeroTierGetToken(c *gin.Context) {
-	username := c.PostForm("username")
-	pwd := c.PostForm("pwd")
-	if len(username) == 0 || len(pwd) == 0 {
-		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-		return
-	}
-	errInfo := service.MyService.ZeroTier().GetToken(username, pwd)
-
-	if len(errInfo) == 0 {
-		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.ERROR, Message: oasis_err2.GetMsg(oasis_err2.GET_TOKEN_ERROR)})
-	} else {
-		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)})
-	}
-}
-
-// @Summary 注册zerotier
-// @Produce  application/json
-// @Accept multipart/form-data
-// @Tags zerotier
-// @Param firstName formData string true "first name"
-// @Param pwd  formData string true "password"
-// @Param email  formData string true "email"
-// @Param lastName  formData string true "last name"
-// @Security ApiKeyAuth
-// @Success 200 {string} string "ok"
-// @Router /zerotier/register [post]
-func ZeroTierRegister(c *gin.Context) {
-	firstName := c.PostForm("firstName")
-	pwd := c.PostForm("pwd")
-	email := c.PostForm("email")
-	lastName := c.PostForm("lastName")
-	if len(firstName) == 0 || len(pwd) == 0 || len(email) == 0 || len(lastName) == 0 {
-		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-		return
-	}
-	errInfo := service.MyService.ZeroTier().ZeroTierRegister(email, lastName, firstName, pwd)
-	if len(errInfo) == 0 {
-		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)})
-	} else {
-		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.ERROR, Message: errInfo})
-	}
-}
-
-// @Summary 是否需要登录zerotier
-// @Produce  application/json
-// @Accept application/json
-// @Tags zerotier
-// @Security ApiKeyAuth
-// @Success 200 {string} string "false:需要登录,true:不需要登录"
-// @Router /zerotier/islogin [get]
-func ZeroTierIsNeedLogin(c *gin.Context) {
-	if len(config.ZeroTierInfo.Token) == 0 {
-		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: false})
-	} else {
-		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: true})
-	}
-}
-
-// @Summary 获取zerotier网络列表
-// @Produce  application/json
-// @Accept application/json
-// @Tags zerotier
-// @Security ApiKeyAuth
-// @Success 200 {string} string "ok"
-// @Router /zerotier/list [get]
-func ZeroTierGetNetworkList(c *gin.Context) {
-	jsonList, joined := service.MyService.ZeroTier().ZeroTierNetworkList(config.ZeroTierInfo.Token)
-	rdata := make(map[string]interface{})
-	rdata["network_list"] = jsonList
-	rdata["joined"] = joined
-	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: rdata})
-}
-
-// @Summary 获取zerotier网络详情
-// @Produce  application/json
-// @Accept application/json
-// @Tags zerotier
-// @Security ApiKeyAuth
-// @Param id path string true "network id"
-// @Success 200 {string} string "ok"
-// @Router /zerotier/info/{id} [get]
-func ZeroTierGetNetworkGetInfo(c *gin.Context) {
-	id := c.Param("id")
-	if len(id) == 0 {
-		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-		return
-	}
-	info, joined := service.MyService.ZeroTier().ZeroTierGetInfo(config.ZeroTierInfo.Token, id)
-	rdata := make(map[string]interface{})
-	rdata["info"] = info
-	rdata["joined"] = joined
-	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: rdata})
-}
-
-//// @Summary 获取zerotier网络状态
-//// @Produce  application/json
-//// @Accept application/json
-//// @Tags zerotier
-//// @Security ApiKeyAuth
-//// @Success 200 {string} string "ok"
-//// @Router /zerotier/status [get]
-//func ZeroTierGetNetworkGetStatus(c *gin.Context) {
-//	status := service.MyService.ZeroTier().ZeroTierGetStatus(config.ZeroTierInfo.Token)
-//	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: status})
-//}
-
-//// @Summary 修改网络类型
-//// @Produce  application/json
-//// @Accept application/json
-//// @Tags zerotier
-//// @Security ApiKeyAuth
-//// @Param id path string true "network id"
-//// @Param type formData string true "Private true/false"
-//// @Success 200 {string} string "ok"
-//// @Router /zerotier/type/{id} [put]
-//func ZeroTierEditType(c *gin.Context) {
-//	id := c.Param("id")
-//	t := c.PostForm("type")
-//	if len(id) == 0 || len(t) == 0 {
-//		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-//		return
-//	}
-//	postData := `{"config":{"private":` + t + `}}`
-//	info := service.MyService.ZeroTier().EditNetwork(config.ZeroTierInfo.Token, postData, id)
-//	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: info})
-//}
-
-//// @Summary 修改名称
-//// @Produce  application/json
-//// @Accept application/json
-//// @Tags zerotier
-//// @Security ApiKeyAuth
-//// @Param id path string true "network id"
-//// @Param name formData string true "需要过滤特殊字符串"
-//// @Success 200 {string} string "ok"
-//// @Router /zerotier/name/{id} [put]
-//func ZeroTierEditName(c *gin.Context) {
-//	id := c.Param("id")
-//	name := c.PostForm("name")
-//	if len(id) == 0 || len(name) == 0 {
-//		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-//		return
-//	}
-//	postData := `{"config":{"name":"` + name + `"}}`
-//	info := service.MyService.ZeroTier().EditNetwork(config.ZeroTierInfo.Token, postData, id)
-//	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: info})
-//}
-
-//// @Summary V6Assign (注意三个属性需要一起传过来,不传的会被zerotier设置成false)
-//// @Produce  application/json
-//// @Accept application/json
-//// @Tags zerotier
-//// @Security ApiKeyAuth
-//// @Param id path string true "network id"
-//// @Param v6plan formData string false "true/false"
-//// @Param rfc formData string false "true/false"
-//// @Param auto formData string false "true/false"
-//// @Success 200 {string} string "ok"
-//// @Router /zerotier/v6assign/{id} [put]
-//func ZeroTierEditV6Assign(c *gin.Context) {
-//	id := c.Param("id")
-//	v6plan := c.PostForm("v6plan")
-//	rfc := c.PostForm("rfc")
-//	auto := c.PostForm("auto")
-//	if len(id) == 0 {
-//		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-//		return
-//	}
-//	var spicing string
-//	if len(v6plan) > 0 {
-//		spicing = `"6plane":` + v6plan
-//	}
-//	if len(rfc) > 0 {
-//		if len(spicing) > 0 {
-//			spicing += ","
-//		}
-//		spicing += `"rfc4193":` + rfc
-//	}
-//
-//	if len(auto) > 0 {
-//		if len(spicing) > 0 {
-//			spicing += ","
-//		}
-//		spicing += `"zt":` + auto
-//	}
-//	postData := `{"config":{"v6AssignMode":{` + spicing + `}}}`
-//	info := service.MyService.ZeroTier().EditNetwork(config.ZeroTierInfo.Token, postData, id)
-//	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: info})
-//}
-
-//// @Summary Broadcast
-//// @Produce  application/json
-//// @Accept application/json
-//// @Tags zerotier
-//// @Security ApiKeyAuth
-//// @Param id path string true "network id"
-//// @Param broadcast formData string true "true/false"
-//// @Success 200 {string} string "ok"
-//// @Router /zerotier/broadcast/{id} [put]
-//func ZeroTierEditBroadcast(c *gin.Context) {
-//	id := c.Param("id")
-//	broadcast := c.PostForm("broadcast")
-//	if len(id) == 0 || len(broadcast) == 0 {
-//		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-//		return
-//	}
-//	postData := `{"config":{"enableBroadcast":` + broadcast + `}}`
-//	info := service.MyService.ZeroTier().EditNetwork(config.ZeroTierInfo.Token, postData, id)
-//	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: info})
-//}
-
-// @Summary 网络列表
-// @Produce  application/json
-// @Accept application/json
-// @Tags zerotier
-// @Security ApiKeyAuth
-// @Param id path string true "network id"
-// @Success 200 {string} string "ok"
-// @Router /zerotier/member/{id} [get]
-func ZeroTierMemberList(c *gin.Context) {
-	id := c.Param("id")
-	if len(id) == 0 {
-		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-		return
-	}
-	info := service.MyService.ZeroTier().MemberList(config.ZeroTierInfo.Token, id)
-	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: info})
-}
-
-// @Summary create new network
-// @Produce  application/json
-// @Accept application/json
-// @Tags zerotier
-// @Security ApiKeyAuth
-// @Success 200 {string} string "ok"
-// @Router /zerotier/create [post]
-func ZeroTierCreateNetwork(c *gin.Context) {
-	info := service.MyService.ZeroTier().CreateNetwork(config.ZeroTierInfo.Token)
-	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: info})
-}
-
-//// @Summary 通过/拒绝客户端
-//// @Produce  application/json
-//// @Accept application/json
-//// @Tags zerotier
-//// @Security ApiKeyAuth
-//// @Param id path string true "network id"
-//// @Param mId path string true "member_id"
-//// @Param auth formData string true "true/false"
-//// @Success 200 {string} string "ok"
-//// @Router /zerotier/member/{id}/auth/{mId} [put]
-//func ZeroTierMemberAuth(c *gin.Context) {
-//	id := c.Param("id")
-//	mId := c.Param("mId")
-//	auth := c.PostForm("auth")
-//	if len(id) == 0 || len(mId) == 0 || len(auth) == 0 {
-//		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-//		return
-//	}
-//	postData := `{"config":{"authorized":` + auth + `}}`
-//	info := service.MyService.ZeroTier().EditNetworkMember(config.ZeroTierInfo.Token, postData, id, mId)
-//	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: info})
-//}
-
-//// @Summary 修改名字
-//// @Produce  application/json
-//// @Accept application/json
-//// @Tags zerotier
-//// @Security ApiKeyAuth
-//// @Param id path string true "network id"
-//// @Param mId path string true "member_id"
-//// @Param name formData string true "name"
-//// @Success 200 {string} string "ok"
-//// @Router /zerotier/member/{id}/name/{mId} [put]
-//func ZeroTierMemberName(c *gin.Context) {
-//	id := c.Param("id")
-//	mId := c.Param("mId")
-//	name := c.PostForm("name")
-//	if len(id) == 0 || len(mId) == 0 || len(name) == 0 {
-//		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-//		return
-//	}
-//	postData := `{"name":"` + name + `"}`
-//	info := service.MyService.ZeroTier().EditNetworkMember(config.ZeroTierInfo.Token, postData, id, mId)
-//	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: info})
-//}
-
-//// @Summary 修改桥接
-//// @Produce  application/json
-//// @Accept application/json
-//// @Tags zerotier
-//// @Security ApiKeyAuth
-//// @Param id path string true "network id"
-//// @Param mId path string true "member_id"
-//// @Param bridge formData string true "true/false"
-//// @Success 200 {string} string "ok"
-//// @Router /zerotier/member/{id}/bridge/{mId} [put]
-//func ZeroTierMemberBridge(c *gin.Context) {
-//	id := c.Param("id")
-//	mId := c.Param("mId")
-//	bridge := c.PostForm("bridge")
-//	if len(id) == 0 || len(mId) == 0 || len(bridge) == 0 {
-//		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-//		return
-//	}
-//	postData := `{"config":{"activeBridge":` + bridge + `}}`
-//	info := service.MyService.ZeroTier().EditNetworkMember(config.ZeroTierInfo.Token, postData, id, mId)
-//	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: info})
-//}
-
-// @Summary 修改网络
-// @Produce  application/json
-// @Accept application/json
-// @Tags zerotier
-// @Security ApiKeyAuth
-// @Param id path string true "network id"
-// @Param json formData string true "json数据"
-// @Success 200 {string} string "ok"
-// @Router /zerotier/edit/{id} [put]
-func ZeroTierEdit(c *gin.Context) {
-	id := c.Param("id")
-	json := c.PostForm("json")
-	if len(id) == 0 || len(json) == 0 {
-		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-		return
-	}
-	info := service.MyService.ZeroTier().EditNetwork(config.ZeroTierInfo.Token, json, id)
-	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: info})
-}
-
-// @Summary 获取已加入的网络
-// @Produce  application/json
-// @Accept application/json
-// @Tags zerotier
-// @Security ApiKeyAuth
-// @Success 200 {string} string "ok"
-// @Router /zerotier/joined/list [get]
-func ZeroTierJoinedList(c *gin.Context) {
-	info := service.MyService.ZeroTier().GetJoinNetworks()
-	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: json2.RawMessage(info)})
-}
-
-// @Summary 修改网络用户信息
-// @Produce  application/json
-// @Accept application/json
-// @Tags zerotier
-// @Security ApiKeyAuth
-// @Param id path string true "network id"
-// @Param mId path string true "mId"
-// @Param json formData string true "json数据"
-// @Success 200 {string} string "ok"
-// @Router /zerotier/member/{id}/edit/{mId} [put]
-func ZeroTierMemberEdit(c *gin.Context) {
-	id := c.Param("id")
-	mId := c.Param("mId")
-	json := c.PostForm("json")
-	if len(id) == 0 || len(json) == 0 || len(mId) == 0 {
-		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-		return
-	}
-	info := service.MyService.ZeroTier().EditNetworkMember(config.ZeroTierInfo.Token, json, id, mId)
-	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: info})
-}
-
-// @Summary 删除网络中的用户
-// @Produce  application/json
-// @Accept application/json
-// @Tags zerotier
-// @Security ApiKeyAuth
-// @Param id path string true "network id"
-// @Param mId path string true "member_id"
-// @Success 200 {string} string "ok"
-// @Router /zerotier/member/{id}/del/{mId} [delete]
-func ZeroTierMemberDelete(c *gin.Context) {
-	id := c.Param("id")
-	mId := c.Param("mId")
-	if len(id) == 0 || len(mId) == 0 {
-		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-		return
-	}
-	info := service.MyService.ZeroTier().DeleteMember(config.ZeroTierInfo.Token, id, mId)
-	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: info})
-}
-
-// @Summary 删除网络
-// @Produce  application/json
-// @Accept application/json
-// @Tags zerotier
-// @Security ApiKeyAuth
-// @Param id path string true "network id"
-// @Success 200 {string} string "ok"
-// @Router /zerotier/network/{id}/del [delete]
-func ZeroTierDeleteNetwork(c *gin.Context) {
-	id := c.Param("id")
-	if len(id) == 0 {
-		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-		return
-	}
-	info := service.MyService.ZeroTier().DeleteNetwork(config.ZeroTierInfo.Token, id)
-	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: info})
-}
-
-// @Summary 加入网络
-// @Produce  application/json
-// @Accept multipart/form-data
-// @Tags zerotier
-// @Param id path string true "network id"
-// @Security ApiKeyAuth
-// @Success 200 {string} string "ok"
-// @Router /zerotier/join/{id} [post]
-func ZeroTierJoinNetwork(c *gin.Context) {
-	networkId := c.Param("id")
-	if len(networkId) != 16 {
-		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-		return
-	}
-	for _, v := range networkId {
-		if !service.MyService.ZeroTier().NetworkIdFilter(v) {
-			c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-			return
-		}
-	}
-	service.MyService.ZeroTier().ZeroTierJoinNetwork(networkId)
-	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)})
-}
-
-// @Summary 获取zerotier网络列表
-// @Produce  application/json
-// @Accept multipart/form-data
-// @Tags zerotier
-// @Param id path string true "network id"
-// @Security ApiKeyAuth
-// @Success 200 {string} string "ok"
-// @Router /zerotier/leave/{id} [post]
-func ZeroTierLeaveNetwork(c *gin.Context) {
-	networkId := c.Param("id")
-
-	if len(networkId) != 16 {
-		c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-		return
-	}
-
-	for _, v := range networkId {
-		if !service.MyService.ZeroTier().NetworkIdFilter(v) {
-			c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)})
-			return
-		}
-	}
-	service.MyService.ZeroTier().ZeroTierLeaveNetwork(networkId)
-
-	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)})
-}

+ 1 - 1
route/v1/zima_info.go

@@ -73,7 +73,7 @@ func NetInfo(c *gin.Context) {
 			if n.Name == netCardName {
 				item := *(*model.IOCountersStat)(unsafe.Pointer(&n))
 				item.State = strings.TrimSpace(service.MyService.ZiMa().GetNetState(n.Name))
-				item.DateTime = time.Now()
+				item.Time = time.Now().Unix()
 				newNet = append(newNet, item)
 				break
 			}

+ 65 - 1
service/casa.go

@@ -21,6 +21,10 @@ type CasaService interface {
 	ShareAppFile(body []byte) string
 	PushHeart(id, t string, language string)
 	PushAppAnalyse(uuid, t string, name, language string)
+	PushConnectionStatus(uuid, err string, from, to, event string)
+	PushUserInfo()
+	GetUserInfoByShareId(shareId string) model.UserInfo
+	GetPersonPublic() (list []model.FriendsModel)
 }
 
 type casaService struct {
@@ -117,7 +121,6 @@ func GetToken() string {
 	}
 	go func() {
 		str := httper2.Get(config.ServerInfo.ServerApi+"/token", nil)
-
 		t <- gjson.Get(str, "data").String()
 	}()
 	auth = <-t
@@ -163,7 +166,68 @@ func (o *casaService) PushAppAnalyse(uuid, t string, name, language string) {
 	json2.Unmarshal([]byte(gjson.Get(infoS, "data").String()), &info)
 
 }
+func (o *casaService) PushConnectionStatus(uuid, err string, from, to, event string) {
+
+	m := model.ConnectionStatus{}
+	m.UUId = uuid
+	m.Error = err
+	m.From = from
+	m.To = to
+	m.Event = event
+	b, _ := json.Marshal(m)
+
+	head := make(map[string]string)
+
+	head["Authorization"] = GetToken()
+
+	infoS := httper2.Post(config.ServerInfo.ServerApi+"/v1/analyse/connect", b, "application/json", head)
+
+	info := model.ServerAppList{}
+	json2.Unmarshal([]byte(gjson.Get(infoS, "data").String()), &info)
+
+}
+func (o *casaService) PushUserInfo() {
+	m := model.UserInfo{}
+	m.Desc = config.UserInfo.Description
+	m.Avatar = config.UserInfo.Avatar
+	m.NickName = config.UserInfo.NickName
+	m.ShareId = config.ServerInfo.Token
+	b, _ := json.Marshal(m)
+
+	head := make(map[string]string)
 
+	head["Authorization"] = GetToken()
+
+	infoS := httper2.Post(config.ServerInfo.ServerApi+"/v1/user/info", b, "application/json", head)
+
+	info := model.ServerAppList{}
+	json2.Unmarshal([]byte(gjson.Get(infoS, "data").String()), &info)
+
+}
+
+func (o *casaService) GetUserInfoByShareId(shareId string) model.UserInfo {
+
+	head := make(map[string]string)
+
+	head["Authorization"] = GetToken()
+
+	infoS := httper2.Get(config.ServerInfo.ServerApi+"/v1/user/info/"+shareId, head)
+
+	info := model.UserInfo{}
+	json2.Unmarshal([]byte(gjson.Get(infoS, "data").String()), &info)
+	return info
+}
+func (o *casaService) GetPersonPublic() (list []model.FriendsModel) {
+	head := make(map[string]string)
+
+	head["Authorization"] = GetToken()
+
+	listS := httper2.Get(config.ServerInfo.ServerApi+"/v1/person/public", head)
+
+	json2.Unmarshal([]byte(gjson.Get(listS, "data").String()), &list)
+
+	return list
+}
 func NewCasaService() CasaService {
 	return &casaService{}
 }

+ 66 - 0
service/download.go

@@ -0,0 +1,66 @@
+package service
+
+import (
+	model2 "github.com/IceWhaleTech/CasaOS/service/model"
+	"gorm.io/gorm"
+)
+
+type DownloadService interface {
+	AddDownloadTask(m model2.PersonDownloadDBModel)   //添加下载任务
+	EditDownloadState(m model2.PersonDownloadDBModel) //只修改状态
+	SaveDownload(m model2.PersonDownloadDBModel)
+	DelDownload(uuid string)
+	GetDownloadById(uuid string) model2.PersonDownloadDBModel
+	GetDownloadListByState(state string) []model2.PersonDownloadDBModel
+	SetDownloadError(m model2.PersonDownloadDBModel)
+	GetDownloadListByPath(m model2.PersonDownloadDBModel) int
+}
+type downloadService struct {
+	db *gorm.DB
+}
+
+func (d *downloadService) GetDownloadListByPath(m model2.PersonDownloadDBModel) int {
+	var list []model2.PersonDownloadDBModel
+	d.db.Select("path").Where("path = ? AND `from` = ? AND state = 0", m.Path, m.From).Find(&list)
+	return len(list)
+}
+
+func (d *downloadService) AddDownloadTask(m model2.PersonDownloadDBModel) {
+
+	d.db.Create(&m)
+}
+func (d *downloadService) EditDownloadState(m model2.PersonDownloadDBModel) {
+
+	d.db.Model(&m).Where("uuid = ?", m.UUID).Update("state", m.State)
+}
+
+//failed during download
+func (d *downloadService) SetDownloadError(m model2.PersonDownloadDBModel) {
+	d.db.Model(&m).Updates(m)
+}
+
+func (d *downloadService) DelDownload(uuid string) {
+	var m model2.PersonDownloadDBModel
+	d.db.Where("uuid = ?", uuid).Delete(&m)
+}
+func (d *downloadService) GetDownloadById(uuid string) model2.PersonDownloadDBModel {
+	var m model2.PersonDownloadDBModel
+	d.db.Model(m).Where("uuid = ?", uuid).First(&m)
+	return m
+}
+func (d *downloadService) GetDownloadListByState(state string) (list []model2.PersonDownloadDBModel) {
+	if len(state) == 0 {
+		d.db.Find(&list)
+	} else {
+		d.db.Where("state = ?", state).Find(&list)
+	}
+
+	return
+}
+
+func (d *downloadService) SaveDownload(m model2.PersonDownloadDBModel) {
+	d.db.Save(&m)
+}
+func NewDownloadService(db *gorm.DB) DownloadService {
+	return &downloadService{db: db}
+}

+ 64 - 0
service/friend.go

@@ -0,0 +1,64 @@
+package service
+
+import (
+	"reflect"
+
+	model2 "github.com/IceWhaleTech/CasaOS/service/model"
+	"gorm.io/gorm"
+)
+
+type FriendService interface {
+	AddFriend(m model2.FriendModel)
+	DeleteFriend(m model2.FriendModel)
+	EditFriendMark(m model2.FriendModel)
+	EditFriendBlock(m model2.FriendModel)
+	GetFriendById(m model2.FriendModel) model2.FriendModel
+	GetFriendList() (list []model2.FriendModel)
+	UpdateAddFriendType(m model2.FriendModel)
+	UpdateOrCreate(m model2.FriendModel)
+}
+
+type friendService struct {
+	db *gorm.DB
+}
+
+func (p *friendService) AddFriend(m model2.FriendModel) {
+	p.db.Create(&m)
+}
+func (p *friendService) DeleteFriend(m model2.FriendModel) {
+	p.db.Where("token = ?", m.Token).Delete(&m)
+}
+func (p *friendService) EditFriendMark(m model2.FriendModel) {
+	p.db.Model(&m).Where("token = ?", m.Token).Update("mark", m.Mark)
+}
+func (p *friendService) EditFriendBlock(m model2.FriendModel) {
+	p.db.Model(&m).Where("token = ?", m.Token).Update("block", m.Block)
+}
+func (p *friendService) GetFriendById(m model2.FriendModel) model2.FriendModel {
+	p.db.Model(m).Where("token = ?", m.Token).First(&m)
+	return m
+}
+
+func (p *friendService) GetFriendList() (list []model2.FriendModel) {
+	p.db.Select("nick_name", "avatar", "profile", "token", "state", "mark", "block", "version").Find(&list)
+	return list
+}
+
+func (p *friendService) UpdateOrCreate(m model2.FriendModel) {
+	friend := model2.FriendModel{}
+	p.db.Where("token = ?", m.Token).First(&friend)
+	if reflect.DeepEqual(friend, model2.FriendModel{}) {
+		p.db.Create(&m)
+	} else {
+		p.db.Model(&m).Updates(m)
+	}
+
+}
+
+func (p *friendService) UpdateAddFriendType(m model2.FriendModel) {
+	p.db.Model(&m).Updates(m)
+}
+
+func NewFriendService(db *gorm.DB) FriendService {
+	return &friendService{db: db}
+}

+ 18 - 18
service/model/o_download.go

@@ -1,24 +1,24 @@
 package model
 
-type PersionDownloadDBModel struct {
+type PersonDownloadDBModel struct {
 	UUID      string `gorm:"column:uuid;primary_key" json:"uuid"`
-	State     int    `json:"state"`     //
-	Type      int    `json:"type"`      //defult 1
-	Name      string `json:"name"`      //file name
-	TempPath  string `json:"temp_path"` //temp path
-	Size      int64  `json:"size"`      //file size
-	Section   string `json:"section"`
-	Length    int    `json:"length"` //slice length
-	Hash      string `json:"hash"`
-	CreatedAt string `gorm:"<-:create;autoCreateTime" json:"created_at"`
-	UpdatedAt string `gorm:"<-:create;<-:update;autoUpdateTime" json:"updated_at"`
+	State     int    `json:"state"`             //
+	Type      int    `json:"type"`              //defult 1
+	Name      string `json:"name"`              //file name
+	Size      int64  `json:"size"`              //file size
+	BlockSize int    `json:"block_size"`        //Size of each file block
+	Length    int    `json:"length"`            //slice length
+	Hash      string `json:"hash"`              //File hash value
+	Error     string `json:"error"`             //
+	From      string `json:"from"`              //Error message
+	Path      string `json:"path"`              //Full path to the file
+	Already   int    `json:"already" gorm:"-"`  //Folder blocks that have been downloaded
+	LocalPath string `json:"local_path"`        //The address where the file is saved after download
+	Duration  int64  `json:"duration" gorm:"-"` //Length of time
+	Created   int64  `gorm:"autoCreateTime" json:"created"`
+	Updated   int64  `gorm:"autoCreateTime;autoUpdateTime" json:"updated"`
 }
 
-func (p *PersionDownloadDBModel) TableName() string {
-	return "o_persion_download"
-}
-
-type PersionFileSectionModel struct {
-	Index int    `json:"index"`
-	Hash  string `json:"hash"`
+func (p *PersonDownloadDBModel) TableName() string {
+	return "o_person_download"
 }

+ 19 - 0
service/model/o_friend.go

@@ -0,0 +1,19 @@
+package model
+
+type FriendModel struct {
+	State     int    `json:"state"` //Reserved
+	CreatedAt int64  `gorm:"autoCreateTime" json:"created_at"`
+	UpdatedAt int64  `gorm:"autoCreateTime;autoUpdateTime" json:"updated_at"`
+	NickName  string `json:"nick_name"`
+	Mark      string `json:"mark"`   //Remarks
+	Block     bool   `json:"block"`  //Disable or not
+	Avatar    string `json:"avatar"` //User avatar
+	Token     string `gorm:"column:token;primary_key" json:"token"`
+	Profile   string `json:"profile"` //Description
+	OnLine    bool   `json:"on_line" gorm:"-"`
+	Version   int    `json:"version"`
+}
+
+func (p *FriendModel) TableName() string {
+	return "o_friend"
+}

+ 21 - 0
service/notify.go

@@ -18,6 +18,7 @@ type NotifyServer interface {
 	DelLog(id string)
 	GetList(c int) (list []model.AppNotify)
 	MarkRead(id string, state int)
+	SendText(m model.AppNotify)
 }
 
 type notifyServer struct {
@@ -102,6 +103,26 @@ func SendMeg() {
 	//	}
 }
 
+func (i notifyServer) SendText(m model.AppNotify) {
+	list := []model.AppNotify{}
+	list = append(list, m)
+	json, _ := json2.Marshal(list)
+	var temp []*websocket.Conn
+	for _, v := range WebSocketConns {
+
+		err := v.WriteMessage(1, json)
+		if err == nil {
+			temp = append(temp, v)
+		}
+	}
+	WebSocketConns = temp
+
+	if len(WebSocketConns) == 0 {
+		SocketRun = false
+	}
+
+}
+
 func NewNotifyService(db *gorm.DB) NotifyServer {
 	return &notifyServer{db: db}
 }

+ 247 - 399
service/person.go

@@ -1,28 +1,23 @@
 package service
 
 import (
+	"bufio"
 	"context"
-	"crypto/cipher"
-	"crypto/rand"
-	"crypto/rsa"
-	"crypto/tls"
-	"crypto/x509"
 	"encoding/json"
-	"encoding/pem"
 	"fmt"
 	"io"
-	"log"
-	"math/big"
 	"net"
 	"os"
-	"path/filepath"
 	"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/file"
 	httper2 "github.com/IceWhaleTech/CasaOS/pkg/utils/httper"
+	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"
@@ -31,21 +26,6 @@ import (
 
 type PersonService interface {
 	GetPersionInfo(token string) (m model.PersionModel, err error)
-	Handshake(m model.ConnectState)
-	Download(m model.MessageModel)
-	GetFileDetail(uuid, path, to string)
-	SendFileData(m model.MessageModel, blockSize int, length int)
-	ReplyGetFileDetail(m model.MessageModel)
-	ReceiveFileData(m model.MessageModel)
-	ReceiveGetFileDetail(m model.MessageModel)
-
-	//------------ database
-	AddDownloadTask(m model2.PersionDownloadDBModel)   //添加下载任务
-	EditDownloadState(m model2.PersionDownloadDBModel) //只修改状态
-	EditDownloading(m model2.PersionDownloadDBModel, section model2.PersionFileSectionModel)
-	SaveDownloadState(m model2.PersionDownloadDBModel)
-	DelDownload(uuid string)
-	GetDownloadById(uuid string) model2.PersionDownloadDBModel
 }
 
 type personService struct {
@@ -53,6 +33,7 @@ type personService struct {
 }
 
 var IpInfo model.PersionModel
+var CancelList map[string]string
 
 func PushIpInfo(token string) {
 
@@ -74,439 +55,306 @@ func (p *personService) GetPersionInfo(token string) (m model.PersionModel, err
 	return
 }
 
-//尝试连接
-func (p *personService) Handshake(m model.ConnectState) {
-	//1先进行udp打通成功
+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: 9901} //注意端口必须固定
-	dstAddr := &net.UDPAddr{
-		IP: net.ParseIP(config.ServerInfo.Handshake), Port: 9527}
-	//DialTCP在网络协议net上连接本地地址laddr和远端地址raddr。net必须是"udp"、"udp4"、"udp6";如果laddr不是nil,将使用它作为本地地址,否则自动选择一个本地地址。
-	//(conn)UDPConn代表一个UDP网络连接,实现了Conn和PacketConn接口
-	conn, err := net.DialUDP("udp", srcAddr, dstAddr)
+		IP: net.IPv4zero, Port: port}
+	var err error
+	UDPConn, err = net.ListenUDP("udp", srcAddr)
 	if err != nil {
 		fmt.Println(err)
 	}
-	b, _ := json.Marshal(m)
-	if _, err = conn.Write(b); err != nil {
-		fmt.Println(err)
-	}
-	data := make([]byte, 1024)
-	//ReadFromUDP从c读取一个UDP数据包,将有效负载拷贝到b,返回拷贝字节数和数据包来源地址。
-	//ReadFromUDP方***在超过一个固定的时间点之后超时,并返回一个错误。
-	n, _, err := conn.ReadFromUDP(data)
+	listener, err := quic.Listen(UDPConn, quic_helper.GetGenerateTLSConfig(config.ServerInfo.Token), quic_helper.GetQUICConfig())
 	if err != nil {
-		fmt.Printf("error during read: %s", err)
+		fmt.Println(err)
 	}
-	conn.Close()
-	toPersion := model.PersionModel{}
-	err = json.Unmarshal(data[:n], &toPersion)
+	defer listener.Close()
+	ctx := context.Background()
+	acceptFailures := 0
+	const maxAcceptFailures = 10
 	if err != nil {
-		fmt.Println(err)
+		panic(err)
 	}
 
-	//websocket 连接
-	// bidirectionHole(srcAddr, &anotherPeer)
-
-	//2udp打洞成功向服务器汇报打洞结果
-	//3转udp打洞
-
-}
-
-func (p *personService) AddDownloadTask(m model2.PersionDownloadDBModel) {
-	p.db.Create(&m)
-}
-func (p *personService) EditDownloadState(m model2.PersionDownloadDBModel) {
-	p.db.Model(&m).Where("uuid = ?", m.UUID).Update("state", m.State)
-}
-
-func (p *personService) EditDownloading(m model2.PersionDownloadDBModel, section model2.PersionFileSectionModel) {
-	b, _ := json.Marshal(section)
-	m.Section = string(b)
-	p.db.Model(&m).Where("uuid = ?", m.UUID).Update("section", m.Section)
-}
-
-func (p *personService) DelDownload(uuid string) {
-	var m model2.PersionDownloadDBModel
-	p.db.Where("uuid = ?", uuid).Delete(&m)
-}
-func (p *personService) GetDownloadById(uuid string) model2.PersionDownloadDBModel {
-	var m model2.PersionDownloadDBModel
-	p.db.Model(m).Where("uuid = ?", uuid).First(&m)
-	return m
-}
-
-func (p *personService) SaveDownloadState(m model2.PersionDownloadDBModel) {
-	p.db.Save(&m)
-}
-
-var ipAddress chan string
+	for {
+		select {
+		case <-ctx.Done():
+			fmt.Println(ctx.Err())
+			return
+		default:
+		}
 
-type sysConn struct {
-	conn   *net.UDPConn
-	header string
-	auth   cipher.AEAD
-}
+		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
+			}
 
-func UDPConnect(ips []string) {
-	quicConfig := &quic.Config{
-		ConnectionIDLength:    12,
-		HandshakeIdleTimeout:  time.Second * 8,
-		MaxIdleTimeout:        time.Second * 45,
-		MaxIncomingStreams:    32,
-		MaxIncomingUniStreams: -1,
-		KeepAlive:             true,
-	}
-	fmt.Println(quicConfig)
-	//PersonUDPMap = make(map[string]*net.UDPAddr)
-	ipAddress = make(chan string)
+			// Slightly increased delay for each failure.
+			time.Sleep(time.Duration(acceptFailures) * time.Second)
 
-	srcAddr := &net.UDPAddr{
-		IP: net.IPv4zero, Port: 9901}
-	fmt.Println(srcAddr)
-	//UDPconn, err := net.ListenUDP("udp", srcAddr)
-	// sysconn := &sysConn{
-	// 	conn:   UDPconn,
-	// 	header: "",
-	// 	auth:   nil,
-	// }
-	// if err != nil {
-	// 	fmt.Println(err)
-	// }
-	// liste, err := quic.Listen(UDPconn, generateTLSConfig(), nil)
-	// if err != nil {
-	// 	fmt.Println(err)
-	// }
-	// ssss, err := liste.Accept(context.Background())
-	// if err != nil {
-	// 	fmt.Println(err)
-	// }
-	// st, err := ssss.AcceptStream(context.Background())
-	// if err != nil {
-	// 	fmt.Println(err)
-	// }
-	// st.Write([]byte("ssss"))
-	qlister, err := quic.ListenAddr("0.0.0.0:9901", generateTLSConfig(), nil)
-	//qlister, err := quic.Listen(UDPconn, nil, nil)
-	if err != nil {
-		fmt.Println("quic错误", qlister)
-	}
-	//session, e := qlister.Accept()
-	sess, err := qlister.Accept(context.Background())
-	sess.SendMessage([]byte("aaaa"))
-	stream, err := sess.AcceptStream(context.Background())
-	stream.Write([]byte("bbb"))
-	//quic.Dial()
-	if err != nil {
-		fmt.Println("quic错误", qlister)
-	}
+			continue
+		}
 
-	if err != nil {
-		fmt.Println("监听错误", err.Error())
-	}
-	for _, v := range ips {
-		dstAddr := &net.UDPAddr{
-			IP: net.ParseIP(v), Port: 9901}
+		acceptFailures = 0
 
-		fmt.Println(v, "开始监听")
+		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
+		}
 
-		//quic.Dial()
+		// 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 AsyncUDPConnect(dstAddr)
+		go ProcessingContent(stream)
 	}
+}
 
+//处理内容
+func ProcessingContent(stream quic.Stream) {
 	for {
-		data := make([]byte, 1024)
-		n, add, err := UDPconn.ReadFromUDP(data)
-		fmt.Println(add)
+		prefixByte := make([]byte, 6)
+		_, err := io.ReadFull(stream, prefixByte)
 		if err != nil {
-			log.Printf("error during read:%s\n", err)
-		} else {
-
-			fmt.Println("收到数据:", string(data[:n]))
-			msg := model.MessageModel{}
-			err := json.Unmarshal(data[:n], &msg)
+			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{}
+			}
+			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)
+			break
+		} else if m.Type == types.PERSONADDFRIEND {
+			fmt.Println("有用户来请求加好友", m)
+			friend := model2.FriendModel{}
+			dataModelByte, _ := json.Marshal(m.Data)
+			err := json.Unmarshal(dataModelByte, &friend)
 			if err != nil {
-				log.Printf("转义错误:%s\n", err)
+				fmt.Println(err)
+				continue
 			}
-			//todo:检查数据库是否为合法请求
-			if msg.Type == "hi" {
-				//add ip
-				//PersonUDPMap[msg.From] = add
-			} else if msg.Type == "browse" {
-				//获取目录结构
-			} else if msg.Type == "file_detail" {
-				MyService.Person().ReplyGetFileDetail(msg)
-			} else if msg.Type == "file_detail_reply" {
-				MyService.Person().ReceiveGetFileDetail(msg)
-			} else if msg.Type == "file_data_reply" {
-				MyService.Person().ReceiveFileData(msg)
+			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 {
+				UDPAddressMap[m.From] = m.Data.(string)
 			} else {
-				fmt.Println("未知事件")
+				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.Version = user.Version
+			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)
+
+			break
+		} else if m.Type == types.PERSONCANCEL {
+			CancelList[m.UUId] = "cancel"
+			break
+		} else {
+			//不应有不做返回的数据
+			//ServiceMessage <- m
+			break
 		}
 	}
-}
+	stream.Close()
 
-// Setup a bare-bones TLS config for the server
-func generateTLSConfig() *tls.Config {
-	key, err := rsa.GenerateKey(rand.Reader, 1024)
-	if err != nil {
-		panic(err)
-	}
-	template := x509.Certificate{SerialNumber: big.NewInt(1)}
-	certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
-	if err != nil {
-		panic(err)
-	}
-	keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
-	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
-
-	tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
-	if err != nil {
-		panic(err)
-	}
-	// return &tls.Config{
-	// 	ClientSessionCache:     globalSessionCache,
-	// 	RootCAs:                root,
-	// 	InsecureSkipVerify:     false,
-	// 	NextProtos:             nil,
-	// 	SessionTicketsDisabled: true,
-	// }
-	return &tls.Config{
-		Certificates: []tls.Certificate{tlsCert},
-		NextProtos:   []string{"quic-echo-example"},
-	}
 }
 
-//首次获取文件信息
-func (p *personService) GetFileList(uuid, path, to string) {
+//文件分片发送
+func SendFileData(stream quic.Stream, filePath, to, uuid string) error {
+	summary := model.FileSummaryModel{}
 
 	msg := model.MessageModel{}
-	msg.Type = "file_list"
-	msg.Data = path
-	msg.To = to
+	msg.Type = types.PERSONSUMMARY
 	msg.From = config.ServerInfo.Token
-	msg.UUId = uuid
-	b, _ := json.Marshal(msg)
-	fmt.Println(b)
-	// if ip, ok := PersonUDPMap[msg.To]; ok {
-	// 	_, err := UDPconn.WriteToUDP(b, ip)
-	// 	if err != nil {
-	// 		fmt.Println("写入错误", err)
-	// 	}
-	// }
-	//接收
-
-}
-
-//首次获取文件信息
-func (p *personService) GetFileDetail(uuid, path, to string) {
-
-	msg := model.MessageModel{}
-	msg.Type = "file_detail"
-	msg.Data = path
 	msg.To = to
-	msg.From = config.ServerInfo.Token
 	msg.UUId = uuid
-	b, _ := json.Marshal(msg)
-	fmt.Println(b)
-	// if ip, ok := PersonUDPMap[msg.To]; ok {
-	// 	_, err := UDPconn.WriteToUDP(b, ip)
-	// 	if err != nil {
-	// 		fmt.Println("写入错误", err)
-	// 	}
-	// }
-	//创建临时文件夹
-	file.MkDir("/oasis/download/" + uuid)
-}
 
-func (p *personService) Download(m model.MessageModel) {
-	fDetail, err := os.Stat("/Users/liangjianli/Documents/images")
-	//发送需要发送的数据摘要
+	fStat, err := os.Stat(filePath)
 	if err != nil {
-		fmt.Println("未获取到文件信息")
-	}
-	summary := model.FileSummaryModel{}
-	summary.Hash = file.GetHashByPath(fDetail.Name())
-	summary.Path = m.Data.(string)
-	summary.BlockSize, summary.Length = file.GetBlockInfo(fDetail.Size())
 
-	msg := model.MessageModel{}
-	msg.Type = "download-reply"
-	msg.Data = summary
-	msg.From = config.ServerInfo.Token
-	msg.UUId = ""
-	b, _ := json.Marshal(msg)
+		summary.Message = err.Error()
 
-	fmt.Println(b)
-
-	// if ip, ok := PersonUDPMap[m.From]; ok {
-	// 	_, err := UDPconn.WriteToUDP(b, ip)
-	// 	if err != nil {
-	// 		fmt.Println("写入错误", err)
-	// 	}
-	// }
-}
+		msg.Data = summary
 
-//receive file data
-func (p *personService) ReceiveFileData(m model.MessageModel) {
-	task := p.GetDownloadById(m.UUId)
-
-	//需要重置参数
-	tempPath := "/oasis/download/" + task.UUID
-	tempFilePath := tempPath + "/" + task.Name
-	fmt.Println(tempFilePath)
-	filePath := "/oasis/download/" + task.Name
-
-	bss, _ := json.Marshal(m.Data)
-	tran := model.TranFileModel{}
-	err := json.Unmarshal(bss, &tran)
-	if err != nil {
-		fmt.Println(err)
-	}
-	// if file.ComparisonHash(tran.Hash) {
-	// 	f, err := os.Create(tempFilePath + strconv.Itoa(tran.Index))
-	// 	if err != nil {
-	// 		fmt.Println("创建文件错误", err)
-	// 	}
-	// 	defer f.Close()
-	// 	//		_, err = f.Write(tran.Data)
-	// 	if err != nil {
-	// 		fmt.Println("写入错误", err, tran.Index)
-	// 	}
-	// }
-	var k int
-	err = filepath.Walk(tempPath, func(filename string, fi os.FileInfo, err error) error { //遍历目录
-		if fi.IsDir() { // 忽略目录
-			return nil
-		}
-		k++
-		return nil
-	})
-	if err != nil {
-		fmt.Println("获取文件错误", err)
-	}
-	if task.Length == k {
-		//err := file.SpliceFiles(tempPath, filePath)
-		if err == nil {
-			if h := file.GetHashByPath(filePath); h == task.Hash {
-				//最终文件比对成功
-				task.State = types.DOWNLOADFINISH
-				p.EditDownloadState(task)
-				//remove temp path
-				file.RMDir(tempPath)
-			}
-		}
+		summaryByte, _ := json.Marshal(msg)
+		summaryPrefixLength := file.PrefixLength(len(summaryByte))
+		summaryData := append(summaryPrefixLength, summaryByte...)
+		stream.Write(summaryData)
+		return err
 	}
 
-}
+	blockSize, length := file.GetBlockInfo(fStat.Size())
 
-//1:say hi
-//2:发送文件名称
-//3:发送数据
+	f, err := os.Open(filePath)
+	if err != nil {
 
-//========================================接收端============================================================================================
+		summary.Message = err.Error()
+		msg.Data = summary
 
-// reply file detail
-func (p *personService) ReplyGetFileDetail(m model.MessageModel) {
-	path := m.Data.(string)
-	f, err := os.Stat(path)
-	if err != nil {
-		fmt.Println(err)
+		summaryByte, _ := json.Marshal(msg)
+		summaryPrefixLength := file.PrefixLength(len(summaryByte))
+		summaryData := append(summaryPrefixLength, summaryByte...)
+		stream.Write(summaryData)
+		return err
 	}
-	summary := model.FileSummaryModel{}
-	summary.Name = f.Name()
-	summary.Size = f.Size()
-	summary.Hash = file.GetHashByPath(path)
-	summary.Path = path
-	summary.BlockSize, summary.Length = file.GetBlockInfo(f.Size())
 
-	msg := model.MessageModel{}
-	msg.Type = "file_detail_reply"
+	//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
-	msg.From = config.ServerInfo.Token
-	msg.To = m.From
-	msg.UUId = m.UUId
-	b, _ := json.Marshal(msg)
-	// if ip, ok := PersonUDPMap[m.To]; ok {
-	// 	_, err := UDPconn.WriteToUDP(b, ip)
-	// 	if err != nil {
-	// 		fmt.Println("写入错误", err)
-	// 	}
-	// }
-	fmt.Println(b)
-	//开始发送数据
-	p.SendFileData(m, summary.BlockSize, summary.Length)
-}
 
-func (p *personService) SendFileData(m model.MessageModel, blockSize int, length int) {
-	path := m.Data.(string)
+	summaryByte, _ := json.Marshal(msg)
+	summaryPrefixLength := file.PrefixLength(len(summaryByte))
+	summaryData := append(summaryPrefixLength, summaryByte...)
+	stream.Write(summaryData)
 
-	f, err := os.Open(path)
-	if err != nil {
-		//读取时移动了文件,需要保存数据到数据库
-		fmt.Println("读取失败", err)
-	}
+	bufferedReader := bufio.NewReader(f)
 	buf := make([]byte, blockSize)
+
+	defer stream.Close()
+
 	for i := 0; i < length; i++ {
+
 		tran := model.TranFileModel{}
-		_, err := f.Read(buf)
+
+		n, err := bufferedReader.Read(buf)
+
 		if err == io.EOF {
 			fmt.Println("读取完毕", err)
 		}
-		tran.Hash = file.GetHashByContent(buf)
-		tran.Index = i + 1
-
-		msg := model.MessageModel{}
-		msg.Type = "file_data_reply"
-		msg.Data = tran
-		msg.From = config.ServerInfo.Token
-		msg.To = m.From
-		msg.UUId = m.UUId
-		b, _ := json.Marshal(msg)
-		// if ip, ok := PersonUDPMap[m.To]; ok {
-		// 	_, err := UDPconn.WriteToUDP(b, ip)
-		// 	if err != nil {
-		// 		fmt.Println("写入错误", err)
-		// 	}
-		// }
-		fmt.Println(b)
-	}
 
-}
-
-// 文件摘要返回
-func (p *personService) ReceiveGetFileDetail(m model.MessageModel) {
-
-	task := p.GetDownloadById("")
-	bss, _ := json.Marshal(m.Data)
-	summary := model.FileSummaryModel{}
-	err := json.Unmarshal(bss, &summary)
-	if err != nil {
-		fmt.Println(err)
-	}
-	task.Hash = summary.Hash
-	task.Length = summary.Length
-	task.Size = summary.Size
-
-	p.SaveDownloadState(task)
-}
-
-func AsyncUDPConnect(dst *net.UDPAddr) {
-	for {
-		time.Sleep(2 * time.Second)
-		if _, err := UDPconn.WriteToUDP([]byte(dst.IP.String()+" is ok"), dst); err != nil {
-			log.Println("send msg fail", err)
-			return
-		} else {
-			fmt.Println(dst.IP)
-			fmt.Println(dst.IP.To4())
+		tran.Hash = file.GetHashByContent(buf[:n])
+		tran.Index = i
+		tran.Length = length
+
+		fileMsg := model.MessageModel{}
+		fileMsg.Type = types.PERSONDOWNLOAD
+		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)
 	}
-}
-func NewPersonService(db *gorm.DB) PersonService {
-	return &personService{db: db}
+	return nil
 }

+ 12 - 6
service/service.go

@@ -21,7 +21,6 @@ type Repository interface {
 	User() UserService
 	Docker() DockerService
 	//Redis() RedisService
-	ZeroTier() ZeroTierService
 	ZiMa() ZiMaService
 	Casa() CasaService
 	Disk() DiskService
@@ -33,6 +32,8 @@ type Repository interface {
 	Shortcuts() ShortcutsService
 	Search() SearchService
 	Person() PersonService
+	Friend() FriendService
+	Download() DownloadService
 }
 
 func NewService(db *gorm.DB, log loger2.OLog) Repository {
@@ -43,7 +44,6 @@ func NewService(db *gorm.DB, log loger2.OLog) Repository {
 		user:   NewUserService(),
 		docker: NewDockerService(log),
 		//redis:      NewRedisService(rp),
-		zerotier:       NewZeroTierService(),
 		zima:           NewZiMaService(),
 		casa:           NewCasaService(),
 		disk:           NewDiskService(log, db),
@@ -55,6 +55,8 @@ func NewService(db *gorm.DB, log loger2.OLog) Repository {
 		shortcuts:      NewShortcutsService(db),
 		search:         NewSearchService(),
 		person:         NewPersonService(db),
+		friend:         NewFriendService(db),
+		download:       NewDownloadService(db),
 	}
 }
 
@@ -64,7 +66,6 @@ type store struct {
 	ddns           DDNSService
 	user           UserService
 	docker         DockerService
-	zerotier       ZeroTierService
 	zima           ZiMaService
 	casa           CasaService
 	disk           DiskService
@@ -76,8 +77,16 @@ type store struct {
 	shortcuts      ShortcutsService
 	search         SearchService
 	person         PersonService
+	friend         FriendService
+	download       DownloadService
 }
 
+func (c *store) Download() DownloadService {
+	return c.download
+}
+func (c *store) Friend() FriendService {
+	return c.friend
+}
 func (c *store) Rely() RelyService {
 	return c.rely
 }
@@ -111,9 +120,6 @@ func (c *store) Docker() DockerService {
 	return c.docker
 }
 
-func (c *store) ZeroTier() ZeroTierService {
-	return c.zerotier
-}
 func (c *store) ZiMa() ZiMaService {
 	return c.zima
 }

+ 0 - 74
service/socket.go

@@ -1,74 +0,0 @@
-package service
-
-import (
-	"encoding/json"
-	"fmt"
-	"net/url"
-	"strings"
-	"time"
-
-	"github.com/IceWhaleTech/CasaOS/model"
-	"github.com/IceWhaleTech/CasaOS/pkg/config"
-	"github.com/gorilla/websocket"
-)
-
-var WebSocketConn *websocket.Conn
-
-func SocketConnect() {
-	Connect()
-	ticker := time.NewTicker(time.Second * 5)
-	defer ticker.Stop()
-	done := make(chan struct{})
-	go func() {
-		defer close(done)
-		for {
-			_, message, err := WebSocketConn.ReadMessage()
-			if err != nil {
-				Connect()
-			}
-			msa := model.MessageModel{}
-			json.Unmarshal(message, &msa)
-			if msa.Type == "connection" {
-				bss, _ := json.Marshal(msa.Data)
-				content := model.PersionModel{}
-				err := json.Unmarshal(bss, &content)
-				fmt.Println(content)
-				fmt.Println(err)
-				//开始尝试udp链接
-				go UDPConnect(content.Ips)
-			}
-		}
-	}()
-
-	msg := model.MessageModel{}
-	msg.Data = config.ServerInfo.Token
-	msg.Type = "refresh"
-	msg.From = config.ServerInfo.Token
-	b, _ := json.Marshal(msg)
-	for {
-
-		select {
-		case <-ticker.C:
-			err := WebSocketConn.WriteMessage(websocket.TextMessage, b)
-			if err != nil {
-				Connect()
-			}
-		case <-done:
-			return
-		}
-
-	}
-}
-
-func Connect() {
-	host := strings.Split(config.ServerInfo.Handshake, "://")
-	u := url.URL{Scheme: "ws", Host: host[1], Path: "/v1/ws"}
-	for {
-		d, _, e := websocket.DefaultDialer.Dial(u.String(), nil)
-		if e == nil {
-			WebSocketConn = d
-			return
-		}
-		time.Sleep(time.Second * 5)
-	}
-}

+ 16 - 0
service/system.go

@@ -18,6 +18,8 @@ type SystemService interface {
 	UpdateAssist()
 	UpSystemPort(port string)
 	GetTimeZone() string
+	UpdateUSBAutoMount(state string)
+	ExecUSBAutoMountShell(state string)
 }
 type systemService struct {
 	log loger.OLog
@@ -37,6 +39,15 @@ func (s *systemService) GetTimeZone() string {
 	return command2.ExecResultStr("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;GetTimeZone")
 }
 
+func (s *systemService) ExecUSBAutoMountShell(state string) {
+	if state == "False" {
+		command2.OnlyExec("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;USB_Remove_File")
+	} else {
+		command2.OnlyExec("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;USB_Move_File")
+	}
+
+}
+
 func (s *systemService) GetSystemConfigDebug() []string {
 	return command2.ExecResultStrArray("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;GetSysInfo")
 }
@@ -51,6 +62,11 @@ func (s *systemService) UpSystemConfig(str string, widget string) {
 	}
 	config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
 }
+func (s *systemService) UpdateUSBAutoMount(state string) {
+	config.ServerInfo.USBAutoMount = state
+	config.Cfg.Section("system").Key("USBAutoMount").SetValue(state)
+	config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
+}
 func (s *systemService) UpSystemPort(port string) {
 	if len(port) > 0 && port != config.ServerInfo.HttpPort {
 		config.Cfg.Section("server").Key("HttpPort").SetValue(port)

+ 284 - 89
service/udpconn.go

@@ -1,10 +1,8 @@
 package service
 
 import (
-	"bufio"
 	"context"
 	"crypto/md5"
-	"crypto/tls"
 	"encoding/hex"
 	"encoding/json"
 	"fmt"
@@ -12,164 +10,361 @@ import (
 	"io/ioutil"
 	"net"
 	"os"
+	path2 "path"
 	"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/file"
+	model2 "github.com/IceWhaleTech/CasaOS/service/model"
+	"github.com/IceWhaleTech/CasaOS/types"
 	"github.com/lucas-clemente/quic-go"
 	uuid "github.com/satori/go.uuid"
 )
 
-var UDPconn *net.UDPConn
+var UDPConn *net.UDPConn
 var PeopleMap map[string]quic.Stream
 var Message chan model.MessageModel
+var UDPAddressMap map[string]string
 
-func Dial(addr string, token string) error {
+func Dial(msg model.MessageModel, server bool) (m model.MessageModel, err error) {
+	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+	defer cancel()
 	Message = make(chan model.MessageModel)
-	quicConfig := &quic.Config{
-		ConnectionIDLength: 4,
-		KeepAlive:          true,
+	_, port, err := net.SplitHostPort(UDPConn.LocalAddr().String())
+	if config.ServerInfo.UDPPort != port {
+		config.ServerInfo.UDPPort = port
+		config.Cfg.Section("server").Key("UDPPort").SetValue(port)
+		config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
 	}
-	tlsConf := &tls.Config{
-		InsecureSkipVerify:     true,
-		NextProtos:             []string{"bench"},
-		SessionTicketsDisabled: true,
+	p, err := strconv.Atoi(port)
+	srcAddr := &net.UDPAddr{
+		IP: net.IPv4zero, Port: p} //注意端口必须固定
+	addr := UDPAddressMap[msg.To]
+	ticket := msg.To
+	if server {
+		addr = config.ServerInfo.Handshake + ":9527"
+		ticket = "bench"
 	}
-	session, err := quic.DialAddr(addr, tlsConf, quicConfig)
-	defer session.CloseWithError(0, "")
+	dstAddr, err := net.ResolveUDPAddr("udp", addr)
+
+	//DialTCP在网络协议net上连接本地地址laddr和远端地址raddr。net必须是"udp"、"udp4"、"udp6";如果laddr不是nil,将使用它作为本地地址,否则自动选择一个本地地址。
+	//(conn)UDPConn代表一个UDP网络连接,实现了Conn和PacketConn接口
+
+	session, err := quic.DialContext(ctx, UDPConn, dstAddr, srcAddr.String(), quic_helper.GetClientTlsConfig(ticket), quic_helper.GetQUICConfig())
 	if err != nil {
-		return err
+		if msg.Type == types.PERSONDOWNLOAD {
+			task := MyService.Download().GetDownloadById(msg.UUId)
+			task.Error = err.Error()
+			task.State = types.DOWNLOADERROR
+			MyService.Download().SetDownloadError(task)
+		}
+		if config.SystemConfigInfo.Analyse != "False" {
+			go MyService.Casa().PushConnectionStatus(msg.UUId, err.Error(), msg.From, msg.To, msg.Type)
+		}
+
+		return m, err
 	}
-	stream, err := session.OpenStreamSync(context.Background())
+
+	stream, err := session.OpenStreamSync(ctx)
 	if err != nil {
-		return err
+		if msg.Type == types.PERSONDOWNLOAD {
+			task := MyService.Download().GetDownloadById(msg.UUId)
+			task.Error = err.Error()
+			task.State = types.DOWNLOADERROR
+			MyService.Download().SetDownloadError(task)
+		}
+		if config.SystemConfigInfo.Analyse != "False" {
+			go MyService.Casa().PushConnectionStatus(msg.UUId, err.Error(), msg.From, msg.To, msg.Type)
+		}
+		session.CloseWithError(1, err.Error())
+		return m, err
 	}
-	SayHello(stream, token)
-	//写
+
+	SayHello(stream, msg.To)
+
+	SendData(stream, msg)
+
 	go ReadContent(stream)
-	//读
-	//结果
-	return nil
+	result := <-Message
+	stream.Close()
+	if config.SystemConfigInfo.Analyse != "False" {
+		go MyService.Casa().PushConnectionStatus(msg.UUId, "OK", msg.From, msg.To, msg.Type)
+	}
+	return result, nil
 }
 
 func SayHello(stream quic.Stream, to string) {
 	msg := model.MessageModel{}
-	msg.Type = "hello"
+	msg.Type = types.PERSONHELLO
 	msg.Data = "hello"
 	msg.To = to
 	msg.From = config.ServerInfo.Token
 	msg.UUId = uuid.NewV4().String()
-	b, _ := json.Marshal(msg)
-	prefixLength := file.PrefixLength(len(b))
-
-	data := append(prefixLength, b...)
-	stream.Write(data)
-}
-
-var pathsss string
-
-//文件分片发送
-func SendFileData(stream quic.Stream, filePath, to, uuid string) error {
-
-	fStat, err := os.Stat(filePath)
-	if err != nil {
-		return err
-	}
-
-	blockSize, length := file.GetBlockInfo(fStat.Size())
-
-	f, err := os.Open(filePath)
-	if err != nil {
-		fmt.Println("读取失败", err)
-		return err
-	}
-	bufferedReader := bufio.NewReader(f)
-	buf := make([]byte, blockSize)
-	for i := 0; i < length; i++ {
-
-		tran := model.TranFileModel{}
-
-		_, err = bufferedReader.Read(buf)
-
-		if err == io.EOF {
-			fmt.Println("读取完毕", err)
-		}
-
-		tran.Hash = file.GetHashByContent(buf)
-		tran.Index = i
-
-		msg := model.MessageModel{}
-		msg.Type = "file_data"
-		msg.Data = tran
-		msg.From = config.ServerInfo.Token
-		msg.To = to
-		msg.UUId = uuid
-		b, _ := json.Marshal(msg)
-		stream.Write(b)
-	}
-	defer stream.Close()
-	return nil
+	SendData(stream, msg)
 }
 
 //发送数据
 func SendData(stream quic.Stream, m model.MessageModel) {
 	b, _ := json.Marshal(m)
-	stream.Write(b)
+	prefixLength := file.PrefixLength(len(b))
+	data := append(prefixLength, b...)
+	stream.Write(data)
 }
 
 //读取数据
 func ReadContent(stream quic.Stream) {
-	path := ""
 	for {
-		prefixByte := make([]byte, 4)
-		c1, err := io.ReadFull(stream, prefixByte)
-		fmt.Println(c1, err)
+		prefixByte := make([]byte, 6)
+		_, err := io.ReadFull(stream, prefixByte)
+		if err != nil {
+			fmt.Println(err)
+			time.Sleep(time.Second * 1)
+			for k, v := range CancelList {
+				tempPath := config.AppInfo.RootPath + "/temp" + "/" + v
+				fmt.Println(file.RMDir(tempPath))
+				delete(CancelList, k)
+			}
+			break
+		}
 		prefixLength, err := strconv.Atoi(string(prefixByte))
-
+		if err != nil {
+			fmt.Println(err)
+			break
+		}
 		messageByte := make([]byte, prefixLength)
-		t, err := io.ReadFull(stream, messageByte)
-		fmt.Println(t, err)
+		_, err = io.ReadFull(stream, messageByte)
+		if err != nil {
+			fmt.Println(err)
+			break
+		}
 		m := model.MessageModel{}
 		err = json.Unmarshal(messageByte, &m)
+		fmt.Println("客户端", m)
 		if err != nil {
 			fmt.Println(err)
+			break
 		}
 
-		//传输数据需要继续读取
-		if m.Type == "file_data" {
+		if m.Type == types.PERSONDOWNLOAD {
+
 			dataModelByte, _ := json.Marshal(m.Data)
 			dataModel := model.TranFileModel{}
 			err := json.Unmarshal(dataModelByte, &dataModel)
-			fmt.Println(err)
+			if err != nil {
+				fmt.Println(err)
+				continue
+			}
 
 			dataLengthByte := make([]byte, 8)
-			t, err = io.ReadFull(stream, dataLengthByte)
+			_, err = io.ReadFull(stream, dataLengthByte)
+			if err != nil {
+				fmt.Println(err)
+				continue
+			}
 			dataLength, err := strconv.Atoi(string(dataLengthByte))
 			if err != nil {
 				fmt.Println(err)
+				continue
 			}
 			dataByte := make([]byte, dataLength)
-			t, err = io.ReadFull(stream, dataByte)
+			_, err = io.ReadFull(stream, dataByte)
 			if err != nil {
 				fmt.Println(err)
+				continue
 			}
 			sum := md5.Sum(dataByte)
 			hash := hex.EncodeToString(sum[:])
 			if dataModel.Hash != hash {
 				fmt.Println("hash不匹配", hash, dataModel.Hash)
+				continue
 			}
+			tempPath := config.AppInfo.RootPath + "/temp" + "/" + m.UUId
+			file.IsNotExistMkDir(tempPath)
+			filepath := tempPath + "/" + strconv.Itoa(dataModel.Index)
+			_, err = os.Stat(filepath)
+
+			if os.IsNotExist(err) {
+				err = ioutil.WriteFile(filepath, dataByte, 0644)
+				task := model2.PersonDownloadDBModel{}
+				task.UUID = m.UUId
+				if err != nil {
+					task.Error = err.Error()
+					task.State = types.DOWNLOADERROR
+					MyService.Download().SetDownloadError(task)
+				}
 
-			filepath := path + strconv.Itoa(dataModel.Index)
+			} else {
+				if file.GetHashByPath(filepath) != dataModel.Hash {
+					os.Remove(filepath)
+					err = ioutil.WriteFile(filepath, dataByte, 0644)
+					task := model2.PersonDownloadDBModel{}
+					task.UUID = m.UUId
+					if err != nil {
+						task.Error = err.Error()
+						task.State = types.DOWNLOADERROR
+						MyService.Download().SetDownloadError(task)
+					}
+				}
+			}
+
+			files, err := ioutil.ReadDir(tempPath)
+			if err != nil {
+				fmt.Println(err)
+				continue
+			}
+			if len(files) >= dataModel.Length {
+				summary := MyService.Download().GetDownloadById(m.UUId)
+				summary.State = types.DOWNLOADFINISH
+				MyService.Download().EditDownloadState(summary)
+				fullPath := file.GetNoDuplicateFileName(path2.Join(summary.LocalPath, summary.Name))
+				file.SpliceFiles(tempPath, fullPath, dataModel.Length, 0)
+				if file.GetHashByPath(fullPath) == summary.Hash {
+					file.RMDir(tempPath)
+					summary.State = types.DOWNLOADFINISHED
+					MyService.Download().EditDownloadState(summary)
+				} else {
+					os.Remove(config.FileSettingInfo.DownloadDir + "/" + summary.Name)
+
+					summary.State = types.DOWNLOADERROR
+					summary.Error = "hash mismatch"
+					MyService.Download().SetDownloadError(summary)
+				}
 
-			err = ioutil.WriteFile(filepath, dataByte, 0644)
-			if dataModel.Index >= (dataModel.Length - 1) {
-				//file.SpliceFiles("", path, dataModel.Length)
 				break
 			}
+		} else if m.Type == types.PERSONSUMMARY {
+
+			dataModel := model.FileSummaryModel{}
+			dataModelByte, _ := json.Marshal(m.Data)
+			err := json.Unmarshal(dataModelByte, &dataModel)
+			if err != nil {
+				fmt.Println(err)
+			}
+
+			task := MyService.Download().GetDownloadById(m.UUId)
+			fullPath := path2.Join(task.LocalPath, task.Name)
+			task.State = types.DOWNLOADING
+			if len(dataModel.Message) > 0 {
+				task.State = types.DOWNLOADERROR
+				task.Error = dataModel.Message
+			}
+			if file.Exists(fullPath) && file.GetHashByPath(fullPath) == dataModel.Hash {
+				task.State = types.DOWNLOADFINISHED
+				go func(from, uuid string) {
+					m := model.MessageModel{}
+					m.Data = ""
+					m.From = config.ServerInfo.Token
+					m.To = from
+					m.Type = types.PERSONCANCEL
+					m.UUId = uuid
+					CancelList[uuid] = uuid
+					Dial(m, false)
+				}(task.From, task.UUID)
+
+			}
+			task.UUID = m.UUId
+			task.Name = dataModel.Name
+			task.Length = dataModel.Length
+			task.Size = dataModel.Size
+			task.BlockSize = dataModel.BlockSize
+			task.Hash = dataModel.Hash
+			task.Type = 0
+			task.From = m.From
+			MyService.Download().SaveDownload(task)
+
+		} else if m.Type == types.PERSONCONNECTION {
+			if len(m.Data.(string)) > 0 {
+				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
+			msg := model.MessageModel{}
+			msg.Type = types.PERSONHELLO
+			msg.Data = ""
+			msg.To = m.From
+			msg.From = config.ServerInfo.Token
+			msg.UUId = m.UUId
+			go Dial(msg, false)
+			Message <- m
+			break
+		} else if m.Type == "get_ip" {
+			notify := model2.AppNotify{}
+			notify.CustomId = m.From
+			if len(m.Data.(string)) == 0 {
+				if _, ok := UDPAddressMap[m.From]; ok {
+					notify.Type = types.NOTIFY_TYPE_PERSION_FIRNED_LEAVE
+					go MyService.Notify().SendText(notify)
+				}
+				delete(UDPAddressMap, m.From)
+				Message <- m
+				break
+			}
+			if _, ok := UDPAddressMap[m.From]; !ok {
+				notify.Type = types.NOTIFY_TYPE_PERSION_FIRNED_LIVE
+				go MyService.Notify().SendText(notify)
+			}
+			UDPAddressMap[m.From] = m.Data.(string)
+			Message <- m
+			break
 		} else {
 			Message <- m
 		}
 	}
 	Message <- model.MessageModel{}
 }
+
+func SendIPToServer() {
+	msg := model.MessageModel{}
+	msg.Type = types.PERSONHELLO
+	msg.Data = ""
+	msg.From = config.ServerInfo.Token
+	msg.To = config.ServerInfo.Token
+	msg.UUId = uuid.NewV4().String()
+
+	Dial(msg, true)
+}
+
+func LoopFriend() {
+	list := MyService.Friend().GetFriendList()
+	for i := 0; i < len(list); i++ {
+		msg := model.MessageModel{}
+		msg.Type = "get_ip"
+		msg.Data = ""
+		msg.From = config.ServerInfo.Token
+		msg.To = list[i].Token
+		msg.UUId = uuid.NewV4().String()
+
+		Dial(msg, true)
+
+		msg.Type = types.PERSONHELLO
+		msg.Data = ""
+		msg.From = config.ServerInfo.Token
+		msg.To = list[i].Token
+		msg.UUId = uuid.NewV4().String()
+		if _, ok := UDPAddressMap[list[i].Token]; ok {
+			go Dial(msg, false)
+		}
+		go func(shareId string) {
+			user := MyService.Casa().GetUserInfoByShareId(shareId)
+			m := model2.FriendModel{}
+			m.Token = shareId
+			friend := MyService.Friend().GetFriendById(m)
+			if friend.Version != user.Version {
+				friend.Avatar = user.Avatar
+				friend.NickName = user.NickName
+				friend.Profile = user.Desc
+				friend.Version = user.Version
+				MyService.Friend().UpdateOrCreate(friend)
+			}
+		}(list[i].Token)
+
+	}
+}

+ 6 - 2
service/user.go

@@ -9,7 +9,7 @@ import (
 )
 
 type UserService interface {
-	SetUser(username, pwd, token, email, desc string) error
+	SetUser(username, pwd, token, email, desc, nickName string) error
 	UpLoadFile(file multipart.File, name string) error
 }
 
@@ -17,7 +17,7 @@ type user struct {
 }
 
 //设置用户名密码
-func (c *user) SetUser(username, pwd, token, email, desc string) error {
+func (c *user) SetUser(username, pwd, token, email, desc, nickName string) error {
 	if len(username) > 0 {
 		config.Cfg.Section("user").Key("UserName").SetValue(username)
 		config.UserInfo.UserName = username
@@ -36,6 +36,10 @@ func (c *user) SetUser(username, pwd, token, email, desc string) error {
 		config.Cfg.Section("user").Key("Description").SetValue(desc)
 		config.UserInfo.Description = desc
 	}
+	if len(nickName) > 0 {
+		config.Cfg.Section("user").Key("NickName").SetValue(nickName)
+		config.UserInfo.NickName = nickName
+	}
 	config.Cfg.SaveTo(config.SystemConfigInfo.ConfigPath)
 	return nil
 }

+ 0 - 353
service/zerotier.go

@@ -1,353 +0,0 @@
-package service
-
-import (
-	"bytes"
-	"errors"
-	"fmt"
-	"io/ioutil"
-	"math/rand"
-	"net/http"
-	"strconv"
-	"strings"
-	"time"
-	"unicode"
-
-	"github.com/IceWhaleTech/CasaOS/pkg/config"
-	command2 "github.com/IceWhaleTech/CasaOS/pkg/utils/command"
-	httper2 "github.com/IceWhaleTech/CasaOS/pkg/utils/httper"
-	"github.com/IceWhaleTech/CasaOS/pkg/zerotier"
-	"github.com/PuerkitoBio/goquery"
-	"github.com/tidwall/gjson"
-)
-
-type ZeroTierService interface {
-	GetToken(username, pwd string) string
-	ZeroTierRegister(email, lastName, firstName, password string) string
-	ZeroTierNetworkList(token string) (interface{}, []string)
-	ZeroTierJoinNetwork(networkId string)
-	ZeroTierLeaveNetwork(networkId string)
-	ZeroTierGetInfo(token, id string) (interface{}, []string)
-	ZeroTierGetStatus(token string) interface{}
-	EditNetwork(token string, data string, id string) interface{}
-	CreateNetwork(token string) interface{}
-	MemberList(token string, id string) interface{}
-	EditNetworkMember(token string, data string, id, mId string) interface{}
-	DeleteMember(token string, id, mId string) interface{}
-	DeleteNetwork(token, id string) interface{}
-	GetJoinNetworks() string
-	NetworkIdFilter(letter rune) bool
-}
-type zerotierStruct struct {
-}
-
-var client http.Client
-
-func (c *zerotierStruct) ZeroTierJoinNetwork(networkId string) {
-	command2.OnlyExec(`zerotier-cli join ` + networkId)
-}
-func (c *zerotierStruct) ZeroTierLeaveNetwork(networkId string) {
-	command2.OnlyExec(`zerotier-cli leave ` + networkId)
-}
-
-//登录并获取token
-func (c *zerotierStruct) GetToken(username, pwd string) string {
-	if len(config.ZeroTierInfo.Token) > 0 {
-		return config.ZeroTierInfo.Token
-	} else {
-		return LoginGetToken(username, pwd)
-	}
-}
-
-func (c *zerotierStruct) ZeroTierRegister(email, lastName, firstName, password string) string {
-
-	url := "https://accounts.zerotier.com/auth/realms/zerotier/protocol/openid-connect/registrations?client_id=zt-central&redirect_uri=https%3A%2F%2Fmy.zerotier.com%2Fapi%2F_auth%2Foidc%2Fcallback&response_type=code&scope=openid+profile+email+offline_access&state=state"
-
-	action, cookies, _ := ZeroTierGet(url, nil, 4)
-	var buff bytes.Buffer
-	buff.WriteString("email=")
-	buff.WriteString(email)
-	buff.WriteString("&password=")
-	buff.WriteString(password)
-	buff.WriteString("&password-confirm=")
-	buff.WriteString(password)
-	buff.WriteString("&user.attributes.marketingOptIn=true")
-	buff.WriteString("&firstName")
-	buff.WriteString(firstName)
-	buff.WriteString("&lastName")
-	buff.WriteString(lastName)
-
-	action, errInfo, _ := ZeroTierPost(buff, action, cookies, false)
-	if len(errInfo) > 0 {
-		return errInfo
-	}
-	action, _, _ = ZeroTierGet(action, cookies, 5)
-	return ""
-}
-
-//固定请求head
-func GetHead() map[string]string {
-	var head = make(map[string]string, 4)
-	head["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
-	head["Accept-Language"] = "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3"
-	head["Connection"] = "keep-alive"
-	head["User-Agent"] = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"
-	return head
-}
-
-//登录并获取token,会出现账号密码错误,和邮箱未验证情况,目前未出现其他情况
-func LoginGetToken(username, pwd string) string {
-	//拿到登录的action
-	var loginUrl = "https://accounts.zerotier.com/auth/realms/zerotier/protocol/openid-connect/auth?client_id=zt-central&redirect_uri=https%3A%2F%2Fmy.zerotier.com%2Fapi%2F_auth%2Foidc%2Fcallback&response_type=code&scope=openid+profile+email+offline_access&state=states"
-	action, cookies, _ := ZeroTierGet(loginUrl, nil, 1)
-	if len(action) == 0 {
-		//没有拿到action,页面结构变了
-		return ""
-	}
-	//登录
-	var str bytes.Buffer
-	str.WriteString("username=")
-	str.WriteString(username)
-	str.WriteString("&password=")
-	str.WriteString(pwd)
-	str.WriteString("&credentialId=&login=Log+In")
-	url, logingErrInfo, _ := ZeroTierPost(str, action, cookies, true)
-
-	action, cookies, isLoginOk := ZeroTierGet(url, cookies, 2)
-
-	if isLoginOk {
-		//登录成功,可以继续调用api
-		randomTokenUrl := "https://my.zerotier.com/api/randomToken"
-		json, _, _ := ZeroTierGet(randomTokenUrl, cookies, 3)
-		//获取一个随机token
-		token := gjson.Get(json, "token")
-
-		userInfoUrl := "https://my.zerotier.com/api/status"
-		json, _, _ = ZeroTierGet(userInfoUrl, cookies, 3)
-		//拿到用户id
-		userId := gjson.Get(json, "user.id")
-
-		//设置新token
-		addTokenUrl := "https://my.zerotier.com/api/user/" + userId.String() + "/token"
-		data := make(map[string]string)
-		rand.Seed(time.Now().UnixNano())
-		data["tokenName"] = "oasis-token-" + strconv.Itoa(rand.Intn(1000))
-		data["token"] = token.String()
-		head := make(map[string]string)
-		head["Content-Type"] = "application/json"
-		_, statusCode := httper2.ZeroTierPost(addTokenUrl, data, head, cookies)
-		if statusCode == http.StatusOK {
-			config.Cfg.Section("zerotier").Key("Token").SetValue(token.String())
-			config.Cfg.SaveTo("conf/conf.ini")
-			config.ZeroTierInfo.Token = token.String()
-		}
-	} else {
-		//登录错误信息
-		if len(logingErrInfo) > 0 {
-			return logingErrInfo
-		} else {
-			//验证邮箱
-			action, _, _ = ZeroTierGet(url, cookies, 5)
-			return "You need to verify your email address to activate your account."
-		}
-	}
-	return ""
-}
-
-// t 1:获取action,2:登录成功后拿session(可能需要验证有了或登录失败) 3:随机生成token 4:注册页面拿action  5:注册成功后拿验证邮箱的地址
-func ZeroTierGet(url string, cookies []*http.Cookie, t uint8) (action string, c []*http.Cookie, isExistSession bool) {
-	isExistSession = false
-	action = ""
-	c = []*http.Cookie{}
-	request, _ := http.NewRequest(http.MethodGet, url, nil)
-	for k, v := range GetHead() {
-		request.Header.Add(k, v)
-	}
-	for _, cookie := range cookies {
-		request.AddCookie(cookie)
-	}
-	resp, err := client.Do(request)
-	if err != nil {
-		return
-	}
-	defer resp.Body.Close()
-	c = resp.Cookies()
-	if t == 1 {
-		doc, err := goquery.NewDocumentFromReader(resp.Body)
-		if err != nil {
-			return
-		}
-		action, _ = doc.Find("#kc-form-login").Attr("action")
-		return
-	} else if t == 2 {
-		for _, cookie := range resp.Cookies() {
-			if cookie.Name == "pgx-session" {
-				isExistSession = true
-				break
-			}
-		}
-		//判断是否登录成功,如果需要验证邮箱,则返回验证邮箱的地址。
-		if resp.StatusCode == http.StatusFound && len(resp.Header.Get("Location")) > 0 {
-			action = resp.Header.Get("Location")
-		}
-		return
-	} else if t == 3 {
-		//返回获取到的字符串
-		byteArr, _ := ioutil.ReadAll(resp.Body)
-		action = string(byteArr)
-	} else if t == 4 {
-		doc, err := goquery.NewDocumentFromReader(resp.Body)
-		if err != nil {
-			return
-		}
-		action, _ = doc.Find("#kc-register-form").Attr("action")
-		return
-
-	} else if t == 5 {
-		doc, _ := goquery.NewDocumentFromReader(resp.Body)
-		fmt.Println(doc.Html())
-		action, _ = doc.Find("#kc-info-wrapper a").Attr("href")
-		return
-	}
-
-	return
-}
-
-//模拟提交表单
-func ZeroTierPost(str bytes.Buffer, action string, cookies []*http.Cookie, isLogin bool) (url, errInfo string, err error) {
-	req, err := http.NewRequest(http.MethodPost, action, strings.NewReader(str.String()))
-	if err != nil {
-		return "", "", errors.New("newrequest error")
-	}
-	for k, v := range GetHead() {
-		req.Header.Set(k, v)
-	}
-	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
-	for _, cookie := range cookies {
-		req.AddCookie(cookie)
-	}
-	res, err := client.Do(req)
-	defer res.Body.Close()
-	if err != nil {
-		return "", "", errors.New("request error")
-	}
-	if !isLogin {
-		//注册成功
-		if res.StatusCode == http.StatusFound && len(res.Header.Get("Location")) > 0 {
-			return res.Header.Get("Location"), "", nil
-		} else {
-			register, _ := goquery.NewDocumentFromReader(res.Body)
-			firstErr := strings.TrimSpace(register.Find("#input-error-firstname").Text())
-			lastErr := strings.TrimSpace(register.Find("#input-error-lastname").Text())
-			emailErr := strings.TrimSpace(register.Find("#input-error-email").Text())
-			pwdErr := strings.TrimSpace(register.Find("#input-error-password").Text())
-			var errD strings.Builder
-			if len(firstErr) > 0 {
-				errD.WriteString(firstErr + ",")
-			}
-			if len(lastErr) > 0 {
-				errD.WriteString(lastErr + ",")
-			}
-			if len(emailErr) > 0 {
-				errD.WriteString(emailErr + ",")
-			}
-			if len(pwdErr) > 0 {
-				errD.WriteString(pwdErr + ",")
-			}
-			return "", errD.String(), nil
-		}
-
-	} else {
-		if res.StatusCode == http.StatusFound && len(res.Header.Get("Location")) > 0 {
-			return res.Header.Get("Location"), "", nil
-		}
-		doc, err := goquery.NewDocumentFromReader(res.Body)
-		if err != nil {
-			return "", "", errors.New("request error")
-		}
-
-		errDesc := doc.Find("#input-error").Text()
-		if len(errDesc) > 0 {
-			return "", strings.TrimSpace(errDesc), nil
-		}
-
-	}
-
-	return "", "", nil
-}
-
-//获取zerotile网络列表和本地用户已加入的网络
-func (c *zerotierStruct) ZeroTierNetworkList(token string) (interface{}, []string) {
-	url := "https://my.zerotier.com/api/network"
-	return zerotier.GetData(url, token), command2.ExecResultStrArray(`zerotier-cli listnetworks | awk 'NR>1 {print $3} {line=$0}'`)
-}
-
-// get network info
-func (c *zerotierStruct) ZeroTierGetInfo(token, id string) (interface{}, []string) {
-	url := "https://my.zerotier.com/api/network/" + id
-	info := zerotier.GetData(url, token)
-	return info, command2.ExecResultStrArray(`zerotier-cli listnetworks | awk 'NR>1 {print $3} {line=$0}'`)
-}
-
-//get status
-func (c *zerotierStruct) ZeroTierGetStatus(token string) interface{} {
-	url := "https://my.zerotier.com/api/v1/status"
-	info := zerotier.GetData(url, token)
-	return info
-}
-
-func (c *zerotierStruct) EditNetwork(token string, data string, id string) interface{} {
-	url := "https://my.zerotier.com/api/v1/network/" + id
-	info := zerotier.PostData(url, token, data)
-	return info
-}
-
-func (c *zerotierStruct) EditNetworkMember(token string, data string, id, mId string) interface{} {
-	url := "https://my.zerotier.com/api/v1/network/" + id + "/member/" + mId
-	info := zerotier.PostData(url, token, data)
-	return info
-}
-
-func (c *zerotierStruct) MemberList(token string, id string) interface{} {
-	url := "https://my.zerotier.com/api/v1/network/" + id + "/member"
-	info := zerotier.GetData(url, token)
-	return info
-}
-
-func (c *zerotierStruct) DeleteMember(token string, id, mId string) interface{} {
-	url := "https://my.zerotier.com/api/v1/network/" + id + "/member/" + mId
-	info := zerotier.DeleteMember(url, token)
-	return info
-}
-
-func (c *zerotierStruct) DeleteNetwork(token, id string) interface{} {
-	url := "https://my.zerotier.com/api/v1/network/" + id
-	info := zerotier.DeleteMember(url, token)
-	return info
-}
-
-func (c *zerotierStruct) CreateNetwork(token string) interface{} {
-	url := "https://my.zerotier.com/api/v1/network"
-	info := zerotier.PostData(url, token, "{}")
-	return info
-}
-
-func (c *zerotierStruct) GetJoinNetworks() string {
-	json := command2.ExecResultStr("source " + config.AppInfo.ProjectPath + "/shell/helper.sh ;GetLocalJoinNetworks")
-	return json
-}
-
-func (c *zerotierStruct) NetworkIdFilter(letter rune) bool {
-	if unicode.IsNumber(letter) || unicode.IsLetter(letter) {
-		return true
-	} else {
-		return false
-	}
-}
-func NewZeroTierService() ZeroTierService {
-	//初始化client
-	client = http.Client{Timeout: 30 * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error {
-		return http.ErrUseLastResponse //禁止重定向
-	},
-	}
-	return &zerotierStruct{}
-}

+ 69 - 2
service/zima_info.go

@@ -4,10 +4,12 @@ import (
 	"fmt"
 	"io/ioutil"
 	"os"
+	"path/filepath"
 	"runtime"
 	"strconv"
 	"strings"
 	"time"
+	"unsafe"
 
 	"github.com/IceWhaleTech/CasaOS/model"
 	"github.com/IceWhaleTech/CasaOS/pkg/config"
@@ -32,12 +34,15 @@ type ZiMaService interface {
 	GetNetState(name string) string
 	GetSysInfo() host.InfoStat
 	GetDirPath(path string) []model.Path
+	GetDirPathOne(path string) (m model.Path)
 	MkdirAll(path string) (int, error)
 	CreateFile(path string) (int, error)
 	RenameFile(oldF, newF string) (int, error)
 	GetCpuInfo() []cpu.InfoStat
 }
 
+var NetArray [][]model.IOCountersStat
+
 type zima struct {
 }
 
@@ -84,10 +89,19 @@ func (c *zima) GetDirPath(path string) []model.Path {
 
 	ls, _ := ioutil.ReadDir(path)
 	dirs := []model.Path{}
-
 	if len(path) > 0 {
 		for _, l := range ls {
-			dirs = append(dirs, model.Path{Name: l.Name(), Path: path + "/" + l.Name(), IsDir: l.IsDir(), Date: l.ModTime(), Size: l.Size()})
+			filePath := filepath.Join(path, l.Name())
+			link, err := filepath.EvalSymlinks(filePath)
+			if err != nil {
+				link = filePath
+			}
+			temp := model.Path{Name: l.Name(), Path: filePath, IsDir: l.IsDir(), Date: l.ModTime(), Size: l.Size()}
+			if filePath != link {
+				file, _ := os.Stat(link)
+				temp.IsDir = file.IsDir()
+			}
+			dirs = append(dirs, temp)
 		}
 	} else {
 		dirs = append(dirs, model.Path{Name: "DATA", Path: "/DATA/", IsDir: true, Date: time.Now()})
@@ -95,6 +109,21 @@ func (c *zima) GetDirPath(path string) []model.Path {
 	return dirs
 }
 
+func (c *zima) GetDirPathOne(path string) (m model.Path) {
+
+	f, err := os.Stat(path)
+
+	if err != nil {
+		return
+	}
+	m.IsDir = f.IsDir()
+	m.Name = f.Name()
+	m.Path = path
+	m.Size = f.Size()
+	m.Date = f.ModTime()
+	return
+}
+
 //获取系统信息
 func (c *zima) GetSysInfo() host.InfoStat {
 	info, _ := host.Info()
@@ -174,3 +203,41 @@ func (c *zima) RenameFile(oldF, newF string) (int, error) {
 func NewZiMaService() ZiMaService {
 	return &zima{}
 }
+
+func LoopNet() {
+	netList := MyService.ZiMa().GetNetInfo()
+
+	nets := MyService.ZiMa().GetNet(true)
+	num := 0
+	for i := 0; i < len(netList); i++ {
+
+		for _, netCardName := range nets {
+
+			if netList[i].Name == netCardName {
+				var netArray []model.IOCountersStat
+				if len(NetArray) < (num + 1) {
+					netArray = []model.IOCountersStat{}
+				} else {
+					netArray = NetArray[num]
+				}
+				item := *(*model.IOCountersStat)(unsafe.Pointer(&netList[i]))
+				item.State = strings.TrimSpace(MyService.ZiMa().GetNetState(netList[i].Name))
+				item.Time = time.Now().Unix()
+
+				if len(netArray) >= 60 {
+					netArray = netArray[1:]
+				}
+				netArray = append(netArray, item)
+				if len(NetArray) < (num + 1) {
+					NetArray = append(NetArray, []model.IOCountersStat{})
+				}
+
+				NetArray[num] = netArray
+
+				num++
+				break
+			}
+		}
+
+	}
+}

+ 5 - 10
shell/assist.sh

@@ -1,13 +1,6 @@
 #!/bin/bash
 
-#add in v0.2.3
-version_0_2_3() {
-  ((EUID)) && sudo_cmd="sudo"
-  $sudo_cmd cp -rf /casaOS/server/shell/11-usb-mount.rules /etc/udev/rules.d/
-  $sudo_cmd chmod +x /casaOS/server/shell/usb-mount.sh
-  $sudo_cmd cp -rf /casaOS/server/shell/usb-mount@.service /etc/systemd/system/
 
-}
 
 # add in v0.2.5
 
@@ -16,7 +9,9 @@ readonly CASA_DEPANDS="curl smartmontools parted fdisk ntfs-3g"
 version_0_2_5() {
   install_depends "$CASA_DEPANDS"
 }
-
+version_0_2_11() {
+  sysctl -w net.core.rmem_max=2500000
+}
 
 #Install Depends
 install_depends() {
@@ -35,6 +30,6 @@ install_depends() {
     fi
 }
 
-version_0_2_3
-
 version_0_2_5
+
+version_0_2_11

+ 18 - 1
shell/helper.sh

@@ -245,9 +245,13 @@ do_umount() {
   if [[ -z ${MOUNT_POINT} ]]; then
     ${log} "Warning: ${DEVICE} is not mounted"
   else
+    /bin/kill -9 $(lsof ${MOUNT_POINT})
     umount -l ${DEVICE}
     ${log} "Unmounted ${DEVICE} from ${MOUNT_POINT}"
-    /bin/rmdir "${MOUNT_POINT}"
+    if [ "`ls -A ${MOUNT_POINT}`" = "" ]; then
+      /bin/rm -fr "${MOUNT_POINT}"
+    fi
+    
     sed -i.bak "\@${MOUNT_POINT}@d" /var/log/usb-mount.track
   fi
 
@@ -325,3 +329,16 @@ TarFolder() {
   #查看固定文件夹大小
   du -sh /DATA
 }
+
+USB_Move_File() {
+  ((EUID)) && sudo_cmd="sudo"
+  $sudo_cmd cp -rf /casaOS/server/shell/11-usb-mount.rules /etc/udev/rules.d/
+  $sudo_cmd chmod +x /casaOS/server/shell/usb-mount.sh
+  $sudo_cmd cp -rf /casaOS/server/shell/usb-mount@.service /etc/systemd/system/
+}
+
+USB_Remove_File() {
+  ((EUID)) && sudo_cmd="sudo"
+  $sudo_cmd rm -fr /etc/udev/rules.d/11-usb-mount.rules
+  $sudo_cmd rm -fr /etc/systemd/system/usb-mount@.service
+}

+ 5 - 2
shell/usb-mount.sh

@@ -12,7 +12,7 @@ DEVBASE=$2
 DEVICE="/dev/${DEVBASE}"
 
 # See if this drive is already mounted, and if so where
-MOUNT_POINT=$(lsblk -o name,mountpoint | grep ${DEVICE} | awk '{print $2}')
+MOUNT_POINT=$(lsblk -l -p -o name,mountpoint | grep ${DEVICE} | awk '{print $2}')
 
 do_mount() {
 
@@ -112,9 +112,12 @@ do_umount() {
   if [[ -z ${MOUNT_POINT} ]]; then
     ${log} "Warning: ${DEVICE} is not mounted"
   else
+    #/bin/kill -9 $(lsof ${MOUNT_POINT})
     umount -l ${DEVICE}
     ${log} "Unmounted ${DEVICE} from ${MOUNT_POINT}"
-    /bin/rmdir "${MOUNT_POINT}"
+    if [ "`ls -A ${MOUNT_POINT}`" = "" ]; then
+      /bin/rm -fr "${MOUNT_POINT}"
+    fi
     sed -i.bak "\@${MOUNT_POINT}@d" /var/log/usb-mount.track
   fi
 

+ 2 - 0
types/notify.go

@@ -10,6 +10,8 @@ const (
 	NOTIFY_TYPE_NEED_CONFIRM
 	NOTIFY_TYPE_ERROR
 	NOTIFY_TYPE_INSTALL_LOG
+	NOTIFY_TYPE_PERSION_FIRNED_LEAVE
+	NOTIFY_TYPE_PERSION_FIRNED_LIVE
 )
 
 const (

+ 9 - 0
types/person.go

@@ -0,0 +1,9 @@
+package types
+
+const PERSONADDFRIEND = "add_user"
+const PERSONDOWNLOAD = "file_data"
+const PERSONSUMMARY = "summary"
+const PERSONCONNECTION = "connection"
+const PERSONDIRECTORY = "directory"
+const PERSONHELLO = "hello"
+const PERSONCANCEL = "cancel" // Cancel Download

+ 1 - 0
types/persion_download.go → types/person_download.go

@@ -6,4 +6,5 @@ const (
 	DOWNLOADPAUSE
 	DOWNLOADFINISH
 	DOWNLOADERROR
+	DOWNLOADFINISHED
 )

+ 2 - 2
types/system.go

@@ -1,5 +1,5 @@
 package types
 
-const CURRENTVERSION = "0.2.10"
+const CURRENTVERSION = "0.3.0"
 
-const BODY = "<li>Added CasaOS own file manager</li><li>Fixed the problem of failed to create storage space</li>"
+const BODY = "<li>Add CasaConnect function, now you can share private files peer-to-peer with your friends.</li><li>Add a widget for network traffic monitoring</li><li>Updated the initial directory of Files to the Root directory</li><li>Fix the application ipv6 opening problem</li>"

文件差异内容过多而无法显示
+ 96 - 0
web/8ee7a98310ee94717fe1.worker.js


二进制
web/img/1-small.1b74d2ba.png


二进制
web/img/Account.1bc4a418.png


+ 0 - 72
web/img/Account.bde47fba.svg

@@ -1,72 +0,0 @@
-<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M0 60C0 26.8629 26.8629 0 60 0V0C93.1371 0 120 26.8629 120 60V60C120 93.1371 93.1371 120 60 120V120C26.8629 120 0 93.1371 0 60V60Z" fill="url(#paint0_linear_582:2091)"/>
-<g filter="url(#filter0_d_582:2091)">
-<g filter="url(#filter1_iiiii_582:2091)">
-<path d="M75.1248 42.125C75.1248 50.4783 68.3532 57.25 59.9999 57.25C51.6465 57.25 44.8749 50.4783 44.8749 42.125C44.8749 33.7717 51.6465 27 59.9999 27C68.3532 27 75.1248 33.7717 75.1248 42.125Z" fill="url(#paint1_linear_582:2091)"/>
-<path d="M34.422 71.7327C41.316 66.1301 50.2453 62.75 59.9998 62.75C69.7544 62.75 78.6837 66.1301 85.5777 71.7327C89.5123 74.9302 89.5123 80.8198 85.5777 84.0174C78.6837 89.6199 69.7544 93 59.9998 93C50.2453 93 41.316 89.6199 34.422 84.0174C30.4874 80.8198 30.4874 74.9302 34.422 71.7327Z" fill="url(#paint2_linear_582:2091)"/>
-</g>
-</g>
-<defs>
-<filter id="filter0_d_582:2091" x="4" y="8" width="112" height="112" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
-<feFlood flood-opacity="0" result="BackgroundImageFix"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feOffset dy="4"/>
-<feGaussianBlur stdDeviation="6"/>
-<feComposite in2="hardAlpha" operator="out"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
-<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_582:2091"/>
-<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_582:2091" result="shape"/>
-</filter>
-<filter id="filter1_iiiii_582:2091" x="27.4711" y="23" width="63.0576" height="72" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
-<feFlood flood-opacity="0" result="BackgroundImageFix"/>
-<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feOffset dx="2" dy="2"/>
-<feGaussianBlur stdDeviation="3"/>
-<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0.495833 0 0 0 0 0.879 0 0 0 0 1 0 0 0 0.4 0"/>
-<feBlend mode="normal" in2="shape" result="effect1_innerShadow_582:2091"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feOffset dx="-4" dy="-4"/>
-<feGaussianBlur stdDeviation="3"/>
-<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0.0205888 0 0 0 0 0.00944442 0 0 0 0 0.566667 0 0 0 0.2 0"/>
-<feBlend mode="normal" in2="effect1_innerShadow_582:2091" result="effect2_innerShadow_582:2091"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feOffset dx="1" dy="1"/>
-<feGaussianBlur stdDeviation="1"/>
-<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0.816667 0 0 0 0 0.945 0 0 0 0 1 0 0 0 0.2 0"/>
-<feBlend mode="normal" in2="effect2_innerShadow_582:2091" result="effect3_innerShadow_582:2091"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feOffset dx="-2" dy="-2"/>
-<feGaussianBlur stdDeviation="2"/>
-<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0.00608333 0 0 0 0 0 0 0 0 0 0.304167 0 0 0 0.2 0"/>
-<feBlend mode="normal" in2="effect3_innerShadow_582:2091" result="effect4_innerShadow_582:2091"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feOffset dx="-1" dy="-1"/>
-<feGaussianBlur stdDeviation="0.5"/>
-<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0.075 0 0 0 0 0.778 0 0 0 0 1 0 0 0 0.2 0"/>
-<feBlend mode="normal" in2="effect4_innerShadow_582:2091" result="effect5_innerShadow_582:2091"/>
-</filter>
-<linearGradient id="paint0_linear_582:2091" x1="60" y1="0" x2="60" y2="120" gradientUnits="userSpaceOnUse">
-<stop stop-color="#5A9CFF"/>
-<stop offset="0.87897" stop-color="#2A23D5"/>
-<stop offset="1" stop-color="#4A3CEC"/>
-</linearGradient>
-<linearGradient id="paint1_linear_582:2091" x1="60" y1="40" x2="59.9998" y2="93" gradientUnits="userSpaceOnUse">
-<stop offset="0.0350902" stop-color="white"/>
-<stop offset="0.525594" stop-color="#B0D4FF"/>
-<stop offset="0.837131" stop-color="#DEEDFF"/>
-<stop offset="1" stop-color="#FEFEFF"/>
-</linearGradient>
-<linearGradient id="paint2_linear_582:2091" x1="60" y1="40" x2="59.9998" y2="93" gradientUnits="userSpaceOnUse">
-<stop offset="0.0350902" stop-color="white"/>
-<stop offset="0.525594" stop-color="#B0D4FF"/>
-<stop offset="0.837131" stop-color="#DEEDFF"/>
-<stop offset="1" stop-color="#FEFEFF"/>
-</linearGradient>
-</defs>
-</svg>

+ 17 - 0
web/img/android-package-archive.c32c4fdb.svg

@@ -0,0 +1,17 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(1)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#9fda1e" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <g transform="matrix(3.7796 0 0 3.7796 -89.043 4.424)" fill="#fff" opacity=".75">
+  <path d="m34.671 7.0315v1.7198c0 0.07329 0.059 0.13229 0.13229 0.13229h0.79374c0.07329 0 0.13229-0.059 0.13229-0.13229v-1.7198zm0.26458 1.0583h0.52916v0.52916h-0.52916z" color="#000000"/>
+  <path d="m35.2-0.11215v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916z" color="#000000"/>
+ </g>
+ <path d="m32.883 44.933 2.1667-2.1667c0.33333-0.33333 0.33333-0.85 0-1.1833-0.33333-0.33333-0.85-0.33333-1.1833 0l-2.4667 2.4667c-1.3166-0.66667-2.8166-1.05-4.4-1.05-1.6 0-3.1001 0.38333-4.4332 1.05l-2.4833-2.4667c-0.33333-0.33333-0.85-0.33333-1.1833 0-0.33333 0.33333-0.33333 0.85 0 1.1833l2.1833 2.1833c-2.4667 1.8167-4.0833 4.7334-4.0833 8.05h20c0-3.3166-1.6167-6.2501-4.1168-8.0667zm-9.4547 4.4953h-1.4286v-1.4286h1.4286zm8.5713 0h-1.4286v-1.4286h1.4286z" enable-background="new" fill="#fff" opacity=".75" stroke-width="1.6667"/>
+</svg>

+ 25 - 25
web/img/android.48a6cf16.svg → web/img/android.149f5693.svg

@@ -1,25 +1,25 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
-<g>
-	<g>
-		<g>
-			<path fill="#A4C639" d="M32,0c17.7,0,32,14.3,32,32S49.7,64,32,64S0,49.7,0,32S14.3,0,32,0z"/>
-		</g>
-	</g>
-	<g>
-		<g>
-			<path fill="#FFFFFF" d="M16.6,24.6c-1.4,0-2.6,1.2-2.6,2.6L14,38c0,1.4,1.2,2.6,2.6,2.6c1.5,0,2.6-1.2,2.6-2.6l0-10.9
-				C19.3,25.7,18.1,24.6,16.6,24.6 M37.8,14.8l1.8-3.3c0.1-0.2,0-0.4-0.1-0.5c-0.2-0.1-0.4,0-0.5,0.1l-1.9,3.3
-				c-1.6-0.7-3.3-1.1-5.1-1.1c-1.8,0-3.6,0.4-5.1,1.1L25,11.2C24.9,11,24.7,11,24.5,11c-0.2,0.1-0.2,0.3-0.1,0.5l1.8,3.3
-				c-3.6,1.8-6,5.3-6,9.3l23.6,0C43.8,20.2,41.4,16.7,37.8,14.8 M26.6,19.9c-0.5,0-1-0.4-1-1c0-0.5,0.4-1,1-1c0.5,0,1,0.4,1,1
-				C27.6,19.5,27.2,19.9,26.6,19.9 M37.4,19.9c-0.5,0-1-0.4-1-1c0-0.5,0.4-1,1-1c0.5,0,1,0.4,1,1C38.4,19.5,37.9,19.9,37.4,19.9
-				 M20.3,25.1l0,16.8c0,1.5,1.3,2.8,2.8,2.8l1.9,0l0,5.7c0,1.4,1.2,2.6,2.6,2.6c1.5,0,2.6-1.2,2.6-2.6l0-5.7l3.5,0l0,5.7
-				c0,1.4,1.2,2.6,2.6,2.6c1.5,0,2.6-1.2,2.6-2.6l0-5.7l1.9,0c1.5,0,2.8-1.2,2.8-2.8l0-16.8L20.3,25.1z M50,27.2
-				c0-1.4-1.2-2.6-2.6-2.6c-1.4,0-2.6,1.2-2.6,2.6l0,10.9c0,1.4,1.2,2.6,2.6,2.6c1.4,0,2.6-1.2,2.6-2.6L50,27.2z"/>
-		</g>
-	</g>
-</g>
-</svg>
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
+<g>
+	<g>
+		<g>
+			<path fill="#A4C639" d="M32,0c17.7,0,32,14.3,32,32S49.7,64,32,64S0,49.7,0,32S14.3,0,32,0z"/>
+		</g>
+	</g>
+	<g>
+		<g>
+			<path fill="#FFFFFF" d="M16.6,24.6c-1.4,0-2.6,1.2-2.6,2.6L14,38c0,1.4,1.2,2.6,2.6,2.6c1.5,0,2.6-1.2,2.6-2.6l0-10.9
+				C19.3,25.7,18.1,24.6,16.6,24.6 M37.8,14.8l1.8-3.3c0.1-0.2,0-0.4-0.1-0.5c-0.2-0.1-0.4,0-0.5,0.1l-1.9,3.3
+				c-1.6-0.7-3.3-1.1-5.1-1.1c-1.8,0-3.6,0.4-5.1,1.1L25,11.2C24.9,11,24.7,11,24.5,11c-0.2,0.1-0.2,0.3-0.1,0.5l1.8,3.3
+				c-3.6,1.8-6,5.3-6,9.3l23.6,0C43.8,20.2,41.4,16.7,37.8,14.8 M26.6,19.9c-0.5,0-1-0.4-1-1c0-0.5,0.4-1,1-1c0.5,0,1,0.4,1,1
+				C27.6,19.5,27.2,19.9,26.6,19.9 M37.4,19.9c-0.5,0-1-0.4-1-1c0-0.5,0.4-1,1-1c0.5,0,1,0.4,1,1C38.4,19.5,37.9,19.9,37.4,19.9
+				 M20.3,25.1l0,16.8c0,1.5,1.3,2.8,2.8,2.8l1.9,0l0,5.7c0,1.4,1.2,2.6,2.6,2.6c1.5,0,2.6-1.2,2.6-2.6l0-5.7l3.5,0l0,5.7
+				c0,1.4,1.2,2.6,2.6,2.6c1.5,0,2.6-1.2,2.6-2.6l0-5.7l1.9,0c1.5,0,2.8-1.2,2.8-2.8l0-16.8L20.3,25.1z M50,27.2
+				c0-1.4-1.2-2.6-2.6-2.6c-1.4,0-2.6,1.2-2.6,2.6l0,10.9c0,1.4,1.2,2.6,2.6,2.6c1.4,0,2.6-1.2,2.6-2.6L50,27.2z"/>
+		</g>
+	</g>
+</g>
+</svg>

+ 17 - 0
web/img/application-apk.319706c4.svg

@@ -0,0 +1,17 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333 0 0 2.3333 -68.667 -72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#84c835" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <g transform="translate(-14.713 -.1522)">
+  <path d="m22.651 6.7667v1.7198c0 0.073288 0.059 0.13229 0.13229 0.13229h0.79374c0.07329 0 0.13229-0.059001 0.13229-0.13229v-1.7198zm0.26458 1.0583h0.52916v0.52916h-0.52916z" color="#000000" fill="#fff"/>
+  <path d="m23.18 1.2105h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916z" color="#000000" fill="#fff"/>
+  <path d="m25.359 10.922 0.80255-0.80255c0.12347-0.12347 0.12347-0.31485 0-0.43832s-0.31485-0.12347-0.43832 0l-0.91368 0.91368c-0.4877-0.24694-1.0433-0.38893-1.6298-0.38893-0.59266 0-1.1483 0.14199-1.6421 0.38893l-0.91985-0.91368c-0.12347-0.12347-0.31485-0.12347-0.43832 0s-0.12347 0.31485 0 0.43832l0.80873 0.80873c-0.91368 0.67291-1.5125 1.7533-1.5125 2.9818h7.4082c0-1.2285-0.59883-2.3151-1.5249-2.988zm-3.5021 1.6651h-0.52916v-0.52916h0.52916zm3.1749 0h-0.52916v-0.52916h0.52916z" enable-background="new" fill="#0c2809" opacity=".5" stroke-width=".61735"/>
+ </g>
+</svg>

+ 21 - 0
web/img/application-certificate.64a6804d.svg

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="64" height="64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <metadata>
+  <rdf:RDF>
+   <cc:Work rdf:about="">
+    <dc:format>image/svg+xml</dc:format>
+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+    <dc:title/>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333 0 0 2.3333 -68.667 -72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <rect x="8" y="4" width="48" height="56" ry="5" fill="#f55" style="paint-order:stroke fill markers"/>
+ <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ <path d="m30.662 18.545-1.291 1.666-2.0508-0.4668-0.55664 2.0293-2.0742 0.35547 0.26367 2.0879-1.7812 1.123 1.0449 1.8281-1.2168 1.7188 1.6621 1.2891-0.46289 2.0527 2.0293 0.55469 0.35547 2.0742 0.8125-0.10352v10.404l4.5938-3.3418 4.5938 3.3418v-9.832l0.09766 0.02344 0.55664-2.0293 2.0742-0.35742-0.26562-2.0879 1.7812-1.1211-1.043-1.8301 1.2148-1.7168-1.6621-1.291 0.46484-2.0508-2.0293-0.55664-0.35547-2.0723-2.0879 0.26367-1.123-1.7812-1.8281 1.043zm1.3379 4.3066a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5z" color="#4d4d4d" color-rendering="auto" fill="#fff" fill-rule="evenodd" image-rendering="auto" shape-rendering="auto" solid-color="#000000" style="isolation:auto;mix-blend-mode:normal"/>
+</svg>

文件差异内容过多而无法显示
+ 11 - 0
web/img/application-dicom.fbef31c7.svg


+ 13 - 0
web/img/application-epub+zip.73eef8b2.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333 0 0 2.3333 -68.667 -72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#84c835" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m8.4664 10.961-2.4942-2.4944 2.4942-2.4941 0.83147 0.83132-1.6629 1.6627 0.83142 0.83147 2.4942-2.4941-2.1727-2.1727c-0.17742-0.17757-0.4652-0.17757-0.64277 0l-3.5142 3.5142c-0.17742 0.17742-0.17742 0.4652 0 0.64277l3.5142 3.5141c0.17757 0.17757 0.46535 0.17757 0.64277 0l3.5142-3.5141c0.17742-0.17757 0.17742-0.46535 0-0.64277l-0.51016-0.51001z" enable-background="new" fill="#fff" stroke-width=".050576"/>
+</svg>

+ 13 - 0
web/img/application-illustrator.2ea791ae.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#341c05" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".5" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path class="st1" d="m6.8166 9.7034-0.3832 1.4552c-0.0097 0.03881-0.02425 0.0485-0.07276 0.0485h-0.71304c-0.04851 0-0.05821-0.01455-0.04851-0.07276l1.3776-4.8215c0.02425-0.08731 0.0388-0.16492 0.04851-0.4026 0-0.03395 0.01455-0.0485 0.0388-0.0485h1.0186c0.03395 0 0.04851 0.0097 0.05821 0.0485l1.5425 5.2338c0.0097 0.03881 0 0.06306-0.0388 0.06306h-0.8052c-0.0388 0-0.06306-0.0097-0.07276-0.04366l-0.4026-1.46zm1.3485-0.7858c-0.13582-0.53842-0.45596-1.7123-0.57722-2.2798h-0.0097c-0.10186 0.56752-0.35894 1.5279-0.56267 2.2798zm2.1828-2.6969c0-0.31044 0.21828-0.49476 0.49476-0.49476 0.29589 0 0.49476 0.19887 0.49476 0.49476 0 0.32014-0.20858 0.49476-0.50446 0.49476-0.28134 0-0.48506-0.17462-0.48506-0.49476zm0.05821 1.1011c0-0.0388 0.01455-0.05821 0.05821-0.05821h0.76154c0.0388 0 0.05821 0.01455 0.05821 0.05821v3.8271c0 0.0388-0.0097 0.05821-0.05821 0.05821h-0.75185c-0.04851 0-0.06306-0.02425-0.06306-0.06306v-3.8223z" enable-background="new" fill="#ff7c00" stroke-width=".048506"/>
+</svg>

+ 22 - 0
web/img/application-json.eea7b6c3.svg

@@ -0,0 +1,22 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+  <linearGradient id="b" x1="-666.12" x2="-553.27" y1="413.04" y2="525.91" gradientTransform="matrix(.99884 0 0 .9987 689.01 -388.84)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
+  <linearGradient id="a">
+   <stop stop-color="#3b3b3b" offset="0"/>
+   <stop stop-color="#fff" offset="1"/>
+  </linearGradient>
+  <linearGradient id="c" x1="-553.27" x2="-666.12" y1="525.91" y2="413.05" gradientTransform="matrix(.99884 0 0 .9987 689.01 -388.84)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
+  <linearGradient id="d" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#f4f4f4" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#d)" fill-rule="evenodd" opacity=".1" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <g transform="matrix(.049608 0 0 .049608 4.4978 4.4978)" enable-background="new" fill-rule="evenodd">
+  <path d="m79.865 119.1c35.398 48.255 70.04-13.469 69.989-50.587-0.0602-43.886-44.541-68.414-70.018-68.414-40.892 0-79.836 33.796-79.836 80.036 0 51.396 44.64 79.865 79.836 79.865-7.9645-1.1468-34.506-6.834-34.863-67.967-0.23987-41.347 13.488-57.866 34.805-50.599 0.47743 0.17707 23.514 9.2645 23.514 38.951 0 29.56-23.427 38.715-23.427 38.715z" color="#000000" fill="url(#b)"/>
+  <path d="m79.823 41.401c-23.39-8.0619-52.043 11.216-52.043 49.829 0 63.048 46.721 68.77 52.384 68.77 40.892 0 79.836-33.796 79.836-80.036 0-51.396-44.64-79.865-79.836-79.865 9.7481-1.35 52.541 10.55 52.541 69.037 0 38.141-31.953 58.905-52.735 50.033-0.47743-0.17707-23.514-9.2645-23.514-38.951 0-29.56 23.367-38.818 23.367-38.818z" color="#000000" fill="url(#c)"/>
+ </g>
+</svg>

+ 13 - 0
web/img/application-msonenote.4772ddbe.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(1)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#994b91" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m22 21c-0.554 0-1 0.446-1 1v20c0 0.554 0.446 1 1 1h18c0.554 0 1-0.446 1-1v-1h1c0.554 0 1-0.446 1-1v-4c0-0.186-0.064344-0.351-0.15234-0.5 0.088-0.149 0.15234-0.314 0.15234-0.5v-4c0-0.186-0.064344-0.351-0.15234-0.5 0.088-0.149 0.15234-0.314 0.15234-0.5v-4c0-0.554-0.446-1-1-1h-1v-3c0-0.554-0.446-1-1-1h-18zm1 3h7v1h-7v-1zm9 0h7v1h-7v-1zm9 2h1v4h-1v-4zm-18 1h7v1h-7v-1zm9 0h7v1h-7v-1zm-9 3h7v1h-7v-1zm9 0h7v1h-7v-1zm9 1h1v4h-1v-4zm-18 2h7v1h-7v-1zm9 0h7v1h-7v-1zm-9 3h7v1h-7v-1zm9 0h7v1h-7v-1zm9 0h1v4h-1v-4zm-18 3h7v1h-7v-1zm9 0h7v1h-7v-1z" color="#000000" color-rendering="auto" fill="#fff" image-rendering="auto" opacity=".75" shape-rendering="auto" solid-color="#000000" style="isolation:auto;mix-blend-mode:normal"/>
+</svg>

+ 18 - 0
web/img/application-msoutlook.0c2789ef.svg

@@ -0,0 +1,18 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(1)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#576dab" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <g transform="translate(-52.837 -2.8601)">
+  <path transform="translate(52.837 2.8601)" d="m32 17.301-13.363 10.025c-0.02875 0.0187-0.054801 0.0418-0.082031 0.0625l-0.015626 0.011719v0.001953c-0.3259 0.2559-0.53906 0.64931-0.53906 1.0977v12.199c2e-6 2 1.5771 2 2 2h24s2 0 2-2v-12.199c0-0.45851-0.22189-0.86016-0.56055-1.1152l0.005859-0.007813-13.445-10.076zm0 6.3984a5 5 0 0 1 5 5v1.5c0 0.831-0.669 1.5-1.5 1.5-0.61296 0-1.1359-0.36612-1.3691-0.89062a3 3 0 0 1-2.1309 0.89062 3 3 0 0 1-3-3 3 3 0 0 1 3-3 3 3 0 0 1 3 3v1.5c0 0.277 0.223 0.5 0.5 0.5s0.5-0.223 0.5-0.5v-1.5a4 4 0 0 0-4-4 4 4 0 0 0-4 4 4 4 0 0 0 4 4h3.5c0.277 0 0.5 0.223 0.5 0.5s-0.223 0.5-0.5 0.5h-3.5a5 5 0 0 1-5-5 5 5 0 0 1 5-5zm0 3a2 2 0 0 0-2 2 2 2 0 0 0 2 2 2 2 0 0 0 2-2 2 2 0 0 0-2-2z" enable-background="new" fill="#fff" opacity=".75"/>
+  <path transform="translate(52.837 2.8601)" d="m22.5 20.699c-0.831 0-1.5 0.66967-1.5 1.5v15.002c0 0.83033 0.669 1.498 1.5 1.498h19c0.831 0 1.5-0.66772 1.5-1.498v-15.002c0-0.83033-0.669-1.5-1.5-1.5h-19zm9.5 3a5 5 0 0 1 5 5v1.5c0 0.831-0.669 1.5-1.5 1.5-0.61296 0-1.1359-0.36612-1.3691-0.89062a3 3 0 0 1-2.1309 0.89062 3 3 0 0 1-3-3 3 3 0 0 1 3-3 3 3 0 0 1 3 3v1.5c0 0.277 0.223 0.5 0.5 0.5s0.5-0.223 0.5-0.5v-1.5a4 4 0 0 0-4-4 4 4 0 0 0-4 4 4 4 0 0 0 4 4h3.5c0.277 0 0.5 0.223 0.5 0.5s-0.223 0.5-0.5 0.5h-3.5a5 5 0 0 1-5-5 5 5 0 0 1 5-5zm0 3a2 2 0 0 0-2 2 2 2 0 0 0 2 2 2 2 0 0 0 2-2 2 2 0 0 0-2-2z" enable-background="new" fill="#fff"/>
+  <path d="m70.837 31.36 26.6 18.2-24.6-2e-4c-1 0-2-0.5-2-2z" enable-background="new" fill="#e8ebf0"/>
+  <path d="m96.837 49.56c2-2e-5 2-2 2-2l-1e-5 -16.2-26.6 18.2z" enable-background="new" fill="#f2f2fa"/>
+ </g>
+</svg>

+ 16 - 0
web/img/application-msword-template.5500dd0c.svg

@@ -0,0 +1,16 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(1)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#4747b5" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <g transform="matrix(3.7796 0 0 3.7796 -75.6 4.949)" fill="#fff" stroke-width=".088193">
+  <path d="m28.768 3.453c-1.2902 0.22976-2.5811 0.53461-3.8713 0.76016 0 1.9626-8.5e-4 3.9261 0 5.8896 1.2835 0.22471 2.5677 0.52447 3.8486 0.75844h0.38441v-7.4082zm-0.49905 2.3996-0.5944 2.4951-0.48788-0.03847c-0.11193-0.55714-0.24233-1.1109-0.34248-1.6706-0.09847 0.54368-0.2265 1.0823-0.33928 1.6226-0.16159-0.0084-0.32392-0.01852-0.48635-0.02946-0.13971-0.74062-0.30388-1.4762-0.43432-2.2185 0.14392-0.0067 0.28868-0.01258 0.4326-0.01763 0.08668 0.53611 0.18512 1.0697 0.26087 1.6066 0.11867-0.55041 0.23994-1.1008 0.35777-1.6512 0.15991-0.0093 0.31979-0.01599 0.47969-0.02441 0.11193 0.56809 0.22634 1.1352 0.34753 1.7008 0.0951-0.58408 0.20033-1.1664 0.30217-1.7496 0.16832-0.0059 0.33662-0.01517 0.5041-0.02527z" enable-background="new"/>
+  <path d="m29.395 4.5113h2.6458v5.2916h-2.6458v-0.52916h2.1166v-0.26458h-2.1166v-0.52916h2.1166v-0.26458h-2.1166v-0.52916h2.1166v-0.26458h-2.1166v-0.52916h2.1166v-0.26458h-2.1166v-0.52823h2.1166v-0.2655h-2.1166v-0.52916h2.1166v-0.26458h-2.1166z" enable-background="new"/>
+ </g>
+</svg>

+ 26 - 0
web/img/application-octet-stream.4dbc6148.svg

@@ -0,0 +1,26 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#f4f4f4" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".1" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <g transform="translate(-1.4e-4,-1.5875)" fill="#b3b3b3">
+  <path d="m5.7553 8.1738h-0.30961v-1.6044q0-0.33776 0.014073-0.6333l-0.47849 0.40813-0.16888-0.2111 0.67552-0.53479h0.26739z"/>
+  <path d="m6.656 6.879q0-0.66145 0.19703-0.98513 0.2111-0.33776 0.61923-0.33776 0.4222 0 0.61923 0.33776 0.2111 0.32369 0.2111 0.98513 0 0.66145-0.2111 0.9992-0.19703 0.32369-0.61923 0.32369-0.40813 0-0.61923-0.32369-0.19703-0.33776-0.19703-0.9992zm1.3229 0q0-0.59108-0.14073-0.81625-0.12666-0.22517-0.36591-0.22517-0.22517 0-0.36591 0.22517-0.12666 0.2111-0.12666 0.81625 0 0.61923 0.12666 0.8444 0.14073 0.2111 0.36591 0.2111 0.23925 0 0.36591-0.22517 0.14073-0.22517 0.14073-0.83032z"/>
+  <path d="m8.6404 6.879q0-0.66145 0.19703-0.98513 0.2111-0.33776 0.61923-0.33776 0.4222 0 0.61923 0.33776 0.2111 0.32369 0.2111 0.98513 0 0.66145-0.2111 0.9992-0.19703 0.32369-0.61923 0.32369-0.40813 0-0.61923-0.32369-0.19703-0.33776-0.19703-0.9992zm1.3229 0q0-0.59108-0.14073-0.81625-0.12666-0.22517-0.36591-0.22517-0.22517 0-0.36591 0.22517-0.12666 0.2111-0.12666 0.81625 0 0.61923 0.12666 0.8444 0.14073 0.2111 0.36591 0.2111 0.23925 0 0.36591-0.22517 0.14073-0.22517 0.14073-0.83032z"/>
+  <path d="m10.625 6.879q0-0.66145 0.19703-0.98513 0.2111-0.33776 0.61922-0.33776 0.4222 0 0.61923 0.33776 0.2111 0.32369 0.2111 0.98513 0 0.66145-0.2111 0.9992-0.19703 0.32369-0.61923 0.32369-0.40812 0-0.61922-0.32369-0.19703-0.33776-0.19703-0.9992zm1.3229 0q0-0.59108-0.14073-0.81625-0.12666-0.22517-0.36591-0.22517-0.22517 0-0.36591 0.22517-0.12666 0.2111-0.12666 0.81625 0 0.61923 0.12666 0.8444 0.14073 0.2111 0.36591 0.2111 0.23925 0 0.36591-0.22517 0.14073-0.22517 0.14073-0.83032z"/>
+  <path d="m4.6717 10.054q0-0.66145 0.19703-0.98513 0.2111-0.33776 0.61923-0.33776 0.4222 0 0.61923 0.33776 0.2111 0.32369 0.2111 0.98513 0 0.66145-0.2111 0.9992-0.19703 0.32369-0.61923 0.32369-0.40813 0-0.61923-0.32369-0.19703-0.33776-0.19703-0.9992zm1.3229 0q0-0.59108-0.14073-0.81625-0.12666-0.22517-0.36591-0.22517-0.22517 0-0.36591 0.22517-0.12666 0.2111-0.12666 0.81625 0 0.61923 0.12666 0.8444 0.14073 0.2111 0.36591 0.2111 0.23925 0 0.36591-0.22517 0.14073-0.22517 0.14073-0.83032z"/>
+  <path d="m7.7397 11.349h-0.30961v-1.6044q0-0.33776 0.014073-0.6333l-0.47849 0.40813-0.16888-0.2111 0.67552-0.53479h0.26739z"/>
+  <path d="m8.6404 10.054q0-0.66145 0.19703-0.98513 0.2111-0.33776 0.61923-0.33776 0.4222 0 0.61923 0.33776 0.2111 0.32369 0.2111 0.98513 0 0.66145-0.2111 0.9992-0.19703 0.32369-0.61923 0.32369-0.40813 0-0.61923-0.32369-0.19703-0.33776-0.19703-0.9992zm1.3229 0q0-0.59108-0.14073-0.81625-0.12666-0.22517-0.36591-0.22517-0.22517 0-0.36591 0.22517-0.12666 0.2111-0.12666 0.81625 0 0.61923 0.12666 0.8444 0.14073 0.2111 0.36591 0.2111 0.23925 0 0.36591-0.22517 0.14073-0.22517 0.14073-0.83032z"/>
+  <path d="m10.625 10.054q0-0.66145 0.19703-0.98513 0.2111-0.33776 0.61922-0.33776 0.4222 0 0.61923 0.33776 0.2111 0.32369 0.2111 0.98513 0 0.66145-0.2111 0.9992-0.19703 0.32369-0.61923 0.32369-0.40812 0-0.61922-0.32369-0.19703-0.33776-0.19703-0.9992zm1.3229 0q0-0.59108-0.14073-0.81625-0.12666-0.22517-0.36591-0.22517-0.22517 0-0.36591 0.22517-0.12666 0.2111-0.12666 0.81625 0 0.61923 0.12666 0.8444 0.14073 0.2111 0.36591 0.2111 0.23925 0 0.36591-0.22517 0.14073-0.22517 0.14073-0.83032z"/>
+  <path d="m4.6617 13.229q0-0.66145 0.19703-0.98513 0.2111-0.33776 0.61923-0.33776 0.4222 0 0.61923 0.33776 0.2111 0.32369 0.2111 0.98513t-0.2111 0.9992q-0.19703 0.32369-0.61923 0.32369-0.40813 0-0.61923-0.32369-0.19703-0.33776-0.19703-0.9992zm1.3229 0q0-0.59108-0.14073-0.81625-0.12666-0.22517-0.36591-0.22517-0.22517 0-0.36591 0.22517-0.12666 0.2111-0.12666 0.81625 0 0.61922 0.12666 0.8444 0.14073 0.2111 0.36591 0.2111 0.23925 0 0.36591-0.22517 0.14073-0.22517 0.14073-0.83032z"/>
+  <path d="m6.6461 13.229q0-0.66145 0.19703-0.98513 0.2111-0.33776 0.61923-0.33776 0.4222 0 0.61923 0.33776 0.2111 0.32369 0.2111 0.98513t-0.2111 0.9992q-0.19703 0.32369-0.61923 0.32369-0.40813 0-0.61923-0.32369-0.19703-0.33776-0.19703-0.9992zm1.3229 0q0-0.59108-0.14073-0.81625-0.12666-0.22517-0.36591-0.22517-0.22517 0-0.36591 0.22517-0.12666 0.2111-0.12666 0.81625 0 0.61922 0.12666 0.8444 0.14073 0.2111 0.36591 0.2111 0.23925 0 0.36591-0.22517 0.14073-0.22517 0.14073-0.83032z"/>
+  <path d="m9.7141 14.524h-0.30961v-1.6044q0-0.33776 0.014073-0.6333l-0.47849 0.40813-0.16888-0.2111 0.67552-0.53478h0.26739z"/>
+  <path d="m10.615 13.229q0-0.66145 0.19703-0.98513 0.2111-0.33776 0.61923-0.33776 0.4222 0 0.61922 0.33776 0.2111 0.32369 0.2111 0.98513t-0.2111 0.9992q-0.19703 0.32369-0.61922 0.32369-0.40813 0-0.61923-0.32369-0.19703-0.33776-0.19703-0.9992zm1.3229 0q0-0.59108-0.14073-0.81625-0.12666-0.22517-0.36591-0.22517-0.22517 0-0.36591 0.22517-0.12666 0.2111-0.12666 0.81625 0 0.61922 0.12666 0.8444 0.14073 0.2111 0.36591 0.2111 0.23925 0 0.36591-0.22517 0.14073-0.22517 0.14073-0.83032z"/>
+ </g>
+</svg>

+ 21 - 0
web/img/application-ogg.776137df.svg

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <metadata>
+  <rdf:RDF>
+   <cc:Work rdf:about="">
+    <dc:format>image/svg+xml</dc:format>
+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+    <dc:title/>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <rect x="8" y="4" width="48" height="56" ry="5" fill="#f9d351" style="paint-order:stroke fill markers"/>
+ <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".1" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ <path d="m41.001 20c-0.35521-0.0038-0.82743 0.05144-1.4557 0.14586l-10.455 1.5986c-2.5242 0.5375-2.5155 0.70467-2.5241 2.8372v17.303c-0.10526-0.07374-0.20912-0.1479-0.31794-0.22084v7.49e-4c-0.80196-0.53742-1.6567-0.91988-2.8047-0.91988-1.438 0-2.2955 0.55061-2.8147 1.2759-0.51913 0.72527-0.69129 1.6394-0.69129 2.3461 0 0.85215 0.29095 1.7616 0.92211 2.4546 0.63116 0.69303 1.6015 1.1784 2.9967 1.1784 1.5755 0 2.6837-0.43994 3.3103-1.2991 0.49328-0.67647 0.76605-1.5803 0.86337-2.695v-0.0032c0.01361-0.13524 0.02343-0.27417 0.03213-0.41727 0.01323-0.22035 0.0102-0.63259 0.0102-0.63259v-13.249c0-1.8927-9.1e-5 -2.0056 2.5248-2.4373l7.4349-1.1662c2.5294-0.33094 2.53-0.1008 2.53 1.3206v12.159c-0.10243-0.07178-0.20361-0.144-0.30947-0.21491v7.49e-4c-0.80196-0.53742-1.6563-0.91988-2.8044-0.91988-1.438 0-2.2959 0.55061-2.815 1.2759-0.51913 0.72523-0.69129 1.6394-0.69129 2.3461 0 0.85215 0.29133 1.7616 0.92245 2.4546 0.63116 0.69306 1.6015 1.1787 2.9967 1.1787 1.5755 0 2.6837-0.44032 3.3103-1.2995 0.49328-0.67647 0.76567-1.5799 0.863-2.6947v-0.0035c0.01361-0.13523 0.02343-0.27379 0.03213-0.41693 0.01323-0.22035 0.01058-0.63293 0.01058-0.63293v-18.432c0-1.66-0.01096-2.2097-1.0765-2.2216z" enable-background="new" fill="#fff" stroke-width="3.7796"/>
+</svg>

+ 13 - 0
web/img/application-pdf.319df017.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#e84f43" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m6.942 9.1745c0.26366-0.51679 0.56331-1.099 0.80315-1.6836l0.09483-0.23076c-0.31327-1.1921-0.50112-2.149-0.3333-2.7677 0.04518-0.16131 0.23201-0.25919 0.43176-0.25919l0.12172 0.0018h0.02234c0.27347-0.0042 0.40223 0.34367 0.41685 0.47892 0.02413 0.22525-0.08008 0.60646-0.08008 0.60646 0-0.15394 6e-3 -0.40268-0.09113-0.61733-0.1131-0.24837-0.22113-0.39674-0.31798-0.42026-0.0489 0.03266-0.09643 0.1003-0.11272 0.23041-0.03372 0.18243-0.04383 0.4127-0.04383 0.5314 0 0.41939 0.08258 0.97295 0.24486 1.5436 0.03061-0.08835 0.05755-0.17324 0.07897-0.25278 0.03328-0.12535 0.24483-0.95628 0.24483-0.95628s-0.05331 1.1061-0.12778 1.4408c-0.01596 0.07072-0.03356 0.14067-0.05189 0.21226 0.2676 0.7477 0.6988 1.415 1.2132 1.8953 0.20281 0.18954 0.45896 0.34238 0.70139 0.48168 0.52942-0.07565 1.0169-0.11138 1.4235-0.10688 0.53953 0.0071 0.93557 0.08691 1.0959 0.24488 0.07852 0.07678 0.11039 0.16951 0.12028 0.27346 0.0023 0.04044-0.01734 0.13546-0.02312 0.15931 0.0058-0.02887 0.0058-0.17083-0.42733-0.30908-0.34111-0.10904-0.9795-0.10565-1.7455-0.02411 0.88603 0.43348 1.7492 0.64886 2.0228 0.51974 0.06688-0.03259 0.148-0.14373 0.148-0.14373s-0.04822 0.21908-0.08284 0.27384c-0.04419 0.0595-0.13086 0.12396-0.21304 0.14569-0.43202 0.11526-1.5566-0.15146-2.537-0.71164-1.0953 0.16131-2.2982 0.45931-3.2625 0.77558-0.94754 1.6606-1.6599 2.4232-2.2394 2.1331l-0.21306-0.10714c-0.08661-0.04954-0.09983-0.17013-0.0798-0.26833 0.06757-0.33057 0.48208-0.82846 1.3147-1.3256 0.08962-0.05423 0.48884-0.26531 0.48884-0.26531s-0.29555 0.28605-0.36479 0.34219c-0.66458 0.5446-1.1551 1.2298-1.1428 1.4954l0.0024 0.02316c0.56454-0.08045 1.4111-1.2295 2.4995-3.3591m0.34492 0.17661c-0.18179 0.34238-0.35948 0.65982-0.52353 0.95129 0.90746-0.3802 1.8841-0.6235 2.8139-0.79634-0.12495-0.08628-0.24624-0.17764-0.36038-0.27422-0.51198-0.43346-0.90232-0.97424-1.1853-1.5433-0.17944 0.48342-0.39281 0.99712-0.7447 1.6626" enable-background="new" fill="#fff" stroke-width=".025916"/>
+</svg>

文件差异内容过多而无法显示
+ 13 - 0
web/img/application-pgp-signature.a482d859.svg


+ 11 - 0
web/img/application-pgp.ef9a65ff.svg

@@ -0,0 +1,11 @@
+<svg width="64" height="64" version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <rect x="8" y="4" width="48" height="56" ry="5" fill="#0093dd" style="paint-order:stroke fill markers"/>
+ <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".2" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ <path d="M32 20c-4.432 0-8 3.568-8 8v4h-1v5.687l.406-.53c.582-.781 2.131-1.897 3.344-2.376.639-.252 2.173-.53 3.906-.719C33.267 33.77 38.2 34 40 32v-4c0-4.432-3.568-8-8-8zm.656.969c.38.022.751.066 1.125.156 1.345.323 1.496.634.188.375-2.06-.408-4.794.508-6.469 2.187-.5.503-1.133 1.334-1.406 1.844-.564 1.052-.475.374.125-.906 1.097-2.341 3.783-3.811 6.437-3.656zM32 24c2.216 0 4 1.784 4 4v4h-8v-4c0-2.216 1.784-4 4-4zm9 9.156l-1.063.875c-.577.492-1.48 1.164-2.03 1.5-1.126.688-3.795 1.656-4.595 1.656-.3 0-.476.073-.406.188.134.216 1.731.119 3.406-.219.55-.11 1.827-.61 2.844-1.125L41 35.094v-1.938zm0 2.156l-.563.782c-1.865 2.571-4.903 3.996-9.125 4.25-2.819.169-1.787.49 1.594.5 2.29.003 2.807-.067 4.188-.532.864-.29 1.581-.512 1.625-.468.157.157-1.533 1.556-2.469 2.031-1.793.91-3.014 1.165-6.25 1.312-2.299.105-3.243.224-3.688.47-.566.311-.192.333 7.032.343H41v-8.688z" fill="#fff" overflow="visible"/>
+</svg>

+ 13 - 0
web/img/application-photoshop.20ed858b.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#262626" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".5" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path class="st1" d="m5.4058 5.8326c0-0.03396 0.06791-0.05821 0.10671-0.05821 0.31044-0.01455 0.77124-0.02425 1.2515-0.02425 1.3436 0 1.8675 0.73729 1.8675 1.6832 0 1.2321-0.89251 1.7608-1.9887 1.7608-0.18432 0-0.24738-0.0097-0.37835-0.0097v1.8626c0 0.0388-0.01455 0.05821-0.05821 0.05821h-0.74214c-0.0388 0-0.05821-0.01455-0.05821-0.05821zm0.86341 2.5757c0.11156 0.0097 0.19888 0.0097 0.3929 0.0097 0.56752 0 1.1011-0.19887 1.1011-0.97012 0-0.61603-0.3832-0.92646-1.0283-0.92646-0.19402 0-0.37835 0.0097-0.46566 0.01455zm4.1715-0.57722c-0.3832 0-0.51416 0.19402-0.51416 0.35409 0 0.17462 0.08731 0.29589 0.60147 0.56267 0.76154 0.36865 0.99922 0.72274 0.99922 1.2418 0 0.7761-0.59177 1.1932-1.3921 1.1932-0.422 0-0.7858-0.08731-0.99437-0.20858-0.03395-0.01455-0.03881-0.0388-0.03881-0.07761v-0.71304c0-0.04851 0.02425-0.06306 0.05821-0.0388 0.30559 0.19888 0.65483 0.28618 0.97497 0.28618 0.3832 0 0.54327-0.16007 0.54327-0.37835 0-0.17462-0.11156-0.32984-0.60148-0.58207-0.68878-0.32984-0.97497-0.66453-0.97497-1.2224 0-0.62573 0.48991-1.1447 1.3388-1.1447 0.41715 0 0.70819 0.06306 0.86826 0.13582 0.03881 0.02425 0.04851 0.06306 0.04851 0.09701v0.66453c0 0.0388-0.02425 0.06306-0.07276 0.04851-0.21343-0.13582-0.52872-0.21828-0.844-0.21828z" enable-background="new" fill="#00c8ff" stroke-width=".048506"/>
+</svg>

+ 13 - 0
web/img/application-postscript.7a2e596a.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#eb253e" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m9.079 4.806-3.0219 3.5789c0.78705 0.12623 2.1162 1.1213 1.9157 2.4429 0.40838-0.9801-0.34159-2.1756-1.0618-2.6582l2.6359-3.1482c-0.14118-0.10395-0.25253-0.15596-0.46785-0.21536zm-1.0166 0.24952c-0.11754 4e-4 -0.20572 0.01781-0.20108 0.01781l-0.96522 1.1063c-0.54203 0.0297-1.0692 0.06686-1.5816 0.11883-0.36383 0.03712-0.6534 0.32669-0.70538 0.7128-0.07425 0.67568-0.11135 1.3885-0.11135 2.1236 0 0.73508 0.0371 1.4479 0.11135 2.131 0.05198 0.43075 0.34156 0.64607 0.70538 0.7128 1.0098 0.16335 2.4726 0.0074 2.4726-1.381 0-1.2994-2.131-1.8563-2.8587-1.4999l3.3635-4.0169c-0.07796-0.01949-0.15904-0.02544-0.22956-0.02522zm2.2194 1.1389s-1.8932 4.2026-2.0789 4.6333c-0.28215 0.63855-0.66826 1.1063-1.2251 1.2697 0.49005 0.02228 0.98007 0.02966 1.485 0.02966 1.1062 0 2.1607-0.05196 3.1482-0.14849 0.36372-0.03712 0.66083-0.33412 0.70538-0.7128 0.08178-0.6831 0.11876-1.3959 0.11876-2.131 0-0.73508-0.03699-1.4479-0.11876-2.1236-0.04455-0.3861-0.34155-0.67567-0.70538-0.7128-0.43065-0.04455-0.87618-0.08173-1.3292-0.10401z" clip-rule="evenodd" enable-background="new" fill="#fff" fill-rule="evenodd" stroke-width=".066145"/>
+</svg>

+ 14 - 0
web/img/application-rss_xml.5d705f41.svg

@@ -0,0 +1,14 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#fc8f36" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m30.581 3.9566-3.1749-3.1749v1.852c0 0.73288 0.59001 1.3229 1.3229 1.3229z" fill="#fff" fill-rule="evenodd" opacity=".35" style="paint-order:stroke fill markers"/>
+ <path d="m3.7381 13.706c0-0.163 0.056-0.3 0.169-0.41a0.56 0.56 0 0 1 0.411-0.168c0.159 0 0.293 0.056 0.403 0.168 0.112 0.11 0.168 0.247 0.168 0.41a0.554 0.554 0 0 1-0.168 0.406 0.548 0.548 0 0 1-0.403 0.165 0.565 0.565 0 0 1-0.411-0.165 0.548 0.548 0 0 1-0.169-0.406m-0.033-2.61v0.962c1.228 0 2.225 0.999 2.225 2.229h0.964c0-1.762-1.43-3.19-3.19-3.19zm1e-3 -2.102v1.063a4.229 4.229 0 0 1 4.224 4.23h1.066a5.294 5.294 0 0 0-5.29-5.293z" fill="#fff" font-size="13.59" font-weight="700"/>
+</svg>

+ 16 - 0
web/img/application-sql.e76bf92b.svg

@@ -0,0 +1,16 @@
+<svg width="64" height="64" version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <rect x="8" y="4" width="48" height="56" ry="5" fill="#f4f4f4" style="paint-order:stroke fill markers"/>
+ <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".1" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ <g fill="#888">
+  <rect x="18" y="24" width="28" height="3" rx="0" ry="0" style="paint-order:markers stroke fill"/>
+  <rect x="18" y="31" width="28" height="3" rx="0" ry="0" style="paint-order:markers stroke fill"/>
+  <rect x="18" y="38" width="28" height="3" rx="0" ry="0" style="paint-order:markers stroke fill"/>
+  <rect x="18" y="45" width="28" height="3" rx="0" ry="0" style="paint-order:markers stroke fill"/>
+ </g>
+</svg>

+ 19 - 0
web/img/application-vnd.appimage.50730ff3.svg

@@ -0,0 +1,19 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#55c1ec" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".1" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <g transform="matrix(.90322 0 0 .90322 .81896 -.26851)" fill="#fff">
+  <path d="m8.467 6.238a3.44 3.44 0 0 0-3.44 3.44 3.44 3.44 0 0 0 3.44 3.439 3.44 3.44 0 0 0 3.44-3.44 3.44 3.44 0 0 0-3.44-3.44zm0 1.393a2.046 2.046 0 0 1 2.046 2.046 2.046 2.046 0 0 1-2.046 2.045 2.046 2.046 0 0 1-2.046-2.045 2.046 2.046 0 0 1 2.046-2.046z" opacity=".15" style="paint-order:stroke markers fill"/>
+  <g transform="translate(-4.2406e-8,-280.07)" style="paint-order:stroke markers fill">
+   <path d="m8.467 286.83a2.91 2.91 0 0 0-2.91 2.91 2.91 2.91 0 0 0 2.91 2.911 2.91 2.91 0 0 0 2.91-2.91 2.91 2.91 0 0 0-2.91-2.91zm0 1.323a1.587 1.587 0 0 1 1.587 1.588 1.587 1.587 0 0 1-1.587 1.587 1.587 1.587 0 0 1-1.588-1.587 1.587 1.587 0 0 1 1.588-1.588z"/>
+   <path d="m7.938 285.64h1.058v1.852h-1.058zm0 6.35h1.058v1.852h-1.058zm4.63-2.778v1.058h-1.852v-1.058zm-6.35 0v1.058h-1.852v-1.058zm5.522 3.055-0.747 0.748-1.31-1.31 0.748-0.748zm-4.49-4.49-0.747 0.748-1.31-1.31 0.748-0.748zm-1.309 5.238-0.748-0.748 1.31-1.31 0.748 0.748zm4.49-4.49-0.748-0.748 1.31-1.31 0.748 0.748z"/>
+  </g>
+ </g>
+</svg>

+ 13 - 0
web/img/application-vnd.flatpak.8a781de6.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#40ace8" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m8.4665 4.3296-3.7041 1.9945v4.2847l3.7041 1.9945 3.7041-1.9945v-4.2847zm0 0.60098 2.8816 1.5516-2.8816 1.5516-2.8816-1.5516z" color="#000000" color-rendering="auto" dominant-baseline="auto" fill="#fff" fill-rule="evenodd" image-rendering="auto" shape-rendering="auto" solid-color="#000000" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal"/>
+</svg>

+ 21 - 0
web/img/application-vnd.flatpak.ref.26e20d79.svg

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="64" height="64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <metadata>
+  <rdf:RDF>
+   <cc:Work rdf:about="">
+    <dc:format>image/svg+xml</dc:format>
+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+    <dc:title/>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <rect x="8" y="4" width="48" height="56" ry="5" fill="#4d88cf" style="paint-order:stroke fill markers"/>
+ <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ <path d="m31.934 16-13.88 8.012-0.054 15.976 14 8.012 14-7.998-0.06-15.99-14.007-8.012zm0 1.27v11.568l1.63 0.974-1.63 0.94-11.748-6.787zm13.066 7.562v14.633l-13 7.443v-14.635z" color="#000000" fill="#fff" overflow="visible" solid-color="#000000" style="isolation:auto;mix-blend-mode:normal"/>
+</svg>

+ 13 - 0
web/img/application-vnd.iccprofile.0c017bee.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(1)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#fad65c" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m32 21c-1.876 7-7 11.134-7 15s3.134 7 7 7 7-3.134 7-7-5.124-8-7-15m4.8125 11.428c0.7688 1.0325 1.1851 2.285 1.1875 3.5723 0 3.3137-2.6863 6-6 6-1.2849-0.0025-2.535-0.41738-3.5664-1.1836 0.51355 0.12001 1.039 0.1816 1.5664 0.18359 3.866 0 7-3.134 7-7-0.0028-0.52949-0.06574-1.057-0.1875-1.5723" color="#4d4d4d" color-rendering="auto" fill="#fff" fill-rule="evenodd" image-rendering="auto" opacity=".9" shape-rendering="auto" solid-color="#000000" style="isolation:auto;mix-blend-mode:normal"/>
+</svg>

+ 29 - 0
web/img/application-vnd.kde.bluedevil-sendfile.a912311c.svg

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <metadata>
+  <rdf:RDF>
+   <cc:Work rdf:about="">
+    <dc:format>image/svg+xml</dc:format>
+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+    <dc:title/>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#567dd2" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m6.9643 6.9697 2.9935 2.9935-1.4968 1.4136v-5.8207l1.4968 1.4136-2.9935 2.6609" fill="none" stroke="#fff" stroke-linecap="square" stroke-width=".33261"/>
+ <g fill="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width=".52916">
+  <circle cx="5.2916" cy="8.4665" r=".26458" style="paint-order:stroke fill markers"/>
+  <circle cx="6.3499" cy="8.4665" r=".26458" style="paint-order:stroke fill markers"/>
+  <circle cx="10.583" cy="8.4665" r=".26458" style="paint-order:stroke fill markers"/>
+  <circle cx="11.641" cy="8.4665" r=".26458" style="paint-order:stroke fill markers"/>
+ </g>
+</svg>

+ 13 - 0
web/img/application-vnd.ms-access.785a7627.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#c92429" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m8.6318 4.7624c-1.2902 0.22976-2.5811 0.53459-3.8713 0.76014 0 1.9626-8.5e-4 3.9261 0 5.8896 1.2835 0.22471 2.5676 0.5245 3.8485 0.75847h0.38446v-7.4082zm1.5215 1.0251c-0.29991 0.0029-0.59981 0.02779-0.89515 0.06847v1.0954c0.82989 0.12523 1.6951 0.09973 2.495-0.17014 0.16933-0.07408 0.38355-0.1667 0.41883-0.37219-0.04762-0.25135-0.32974-0.34576-0.54052-0.42426-0.47844-0.14607-0.97833-0.20218-1.4782-0.19727zm2.0179 1.0475c-0.31397 0.25223-0.71884 0.33605-1.1069 0.39338-0.59883 0.07849-1.2074 0.07934-1.8062 0.0035v1.1429c0.74788 0.09613 1.516 0.09183 2.2524-0.08191 0.22577-0.06438 0.47632-0.1315 0.63419-0.31935 0.05644-0.3757 0.0071-0.75931 0.02648-1.1385zm-5.0809 0.25747c0.29544 0.98776 0.57597 1.9809 0.86789 2.9695-0.20372-0.01323-0.40755-0.02825-0.60951-0.04677-0.0538-0.22048-0.11103-0.44094-0.16924-0.66054-0.27075-8.75e-4 -0.54156-0.0098-0.81143-0.023-0.05203 0.20902-0.10498 0.41714-0.15877 0.62527-0.17286-0.01323-0.34478-0.02548-0.51675-0.03695 0.25752-0.92867 0.52557-1.8538 0.78133-2.7825 0.20549-0.01499 0.41012-0.02921 0.61649-0.04509zm-0.33163 0.52838c-0.05909 0.41892-0.19139 0.82191-0.28047 1.2347 0.19402 0.0017 0.38812 0.0026 0.58303 0.0035-0.10054-0.41274-0.22319-0.82011-0.30256-1.2381zm5.4125 0.62527c-0.3122 0.25135-0.71442 0.33515-1.1016 0.39247-0.60059 0.07938-1.2109 0.08112-1.8115 0.0044v1.1438c0.74964 0.09525 1.5204 0.09089 2.2586-0.08462 0.22489-0.06438 0.47533-0.1314 0.62967-0.32013 0.05203-0.3757 0.0072-0.75761 0.02481-1.136zm0 1.411c-0.31397 0.25135-0.71703 0.33603-1.1051 0.39247-0.59971 0.07938-1.2083 0.08112-1.808 0.0044 0 0.38011 0.0027 0.75933-0.0035 1.1394 0.6297 0.09084 1.2726 0.08915 1.9014-7e-3 0.34307-0.06967 0.73909-0.11994 0.98868-0.39157 0.05556-0.3757 8e-3 -0.75942 0.02648-1.1378z" enable-background="new" fill="#fff" stroke-width=".088193"/>
+</svg>

+ 32 - 0
web/img/application-vnd.ms-cab-compressed.011f2e86.svg

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <metadata>
+  <rdf:RDF>
+   <cc:Work rdf:about="">
+    <dc:format>image/svg+xml</dc:format>
+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+    <dc:title/>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#eca973" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <g transform="translate(-26.734 1.1705)" fill="#fff" opacity=".75">
+  <path d="m34.671 7.0315v1.7198c0 0.07329 0.059 0.13229 0.13229 0.13229h0.79374c0.07329 0 0.13229-0.059 0.13229-0.13229v-1.7198zm0.26458 1.0583h0.52916v0.52916h-0.52916z" color="#000000"/>
+  <path d="m35.2-0.11215v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916z" color="#000000"/>
+ </g>
+ <path d="m3.7041 12.171v0.26458h3.1749v-0.26458zm0 0.52916v0.26458h2.6458v-0.26458zm0 0.52916v0.26458h3.1749v-0.26458zm0 0.52916v0.26458h2.1166v-0.26458z" color="#000000" fill="#d78a4d"/>
+ <g transform="matrix(1.25 0 0 1.25 -.92603 -2.5796)" fill="#d78a4d" stroke-width=".034981" aria-label="CAB">
+  <path d="m4.5267 10.416v0.14553q-0.069689-0.06491-0.14894-0.09702-0.078571-0.03211-0.16739-0.03211-0.1749 0-0.26782 0.10727-0.092918 0.10658-0.092918 0.30882 0 0.20155 0.092918 0.30882 0.092918 0.10658 0.26782 0.10658 0.088819 0 0.16739-0.03211 0.079254-0.03211 0.14894-0.09702v0.14416q-0.072422 0.04919-0.15373 0.07379-0.08062 0.0246-0.17081 0.0246-0.23161 0-0.36484-0.14143-0.13323-0.14211-0.13323-0.38739 0-0.24596 0.13323-0.38739 0.13323-0.14211 0.36484-0.14211 0.091552 0 0.17217 0.0246 0.081304 0.02391 0.15236 0.07242z"/>
+  <path d="m5.0808 10.473-0.1872 0.50763h0.37509zm-0.077887-0.13596h0.15646l0.38875 1.0201h-0.14348l-0.092918-0.26167h-0.45981l-0.092918 0.26167h-0.14553z"/>
+  <path d="m5.8351 10.87v0.37372h0.22136q0.11137 0 0.16466-0.04578 0.053975-0.04646 0.053975-0.14143 0-0.09565-0.053975-0.14074-0.053291-0.04578-0.16466-0.04578zm0-0.4195v0.30745h0.20428q0.10112 0 0.15031-0.03758 0.049875-0.03826 0.049875-0.11615 0-0.0772-0.049875-0.11546-0.049192-0.03826-0.15031-0.03826zm-0.13801-0.11342h0.35254q0.15782 0 0.24323 0.06559 0.085403 0.06559 0.085403 0.18652 0 0.0936-0.043726 0.14894-0.043726 0.05534-0.12845 0.06901 0.1018 0.02186 0.15782 0.09155 0.056707 0.069 0.056707 0.17286 0 0.13664-0.092918 0.21112-0.092918 0.07447-0.26441 0.07447h-0.36621z"/>
+ </g>
+</svg>

+ 16 - 0
web/img/application-vnd.ms-excel.ec28c59f.svg

@@ -0,0 +1,16 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#008a50" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <g transform="translate(-20.433 -.26906)" fill="#fff" stroke-width=".088193">
+  <path d="m29.199 5.0315c-1.2902 0.22976-2.5811 0.53459-3.8713 0.76014 0 1.9626-8.4e-4 3.9261 0 5.8896 1.2835 0.22471 2.5676 0.5245 3.8485 0.75847h0.38446v-7.4082zm-0.86117 2.3092c-0.22469 0.46033-0.45024 0.9207-0.67914 1.3793 0.23143 0.47127 0.46794 0.93908 0.7002 1.4104-0.20281-0.01178-0.40476-0.02445-0.60757-0.03876-0.14306-0.35093-0.31732-0.68998-0.41999-1.0569-0.11445 0.34167-0.27773 0.66395-0.40901 0.99889-0.1843-0.0025-0.36863-0.01013-0.55293-0.0177 0.21628-0.4233 0.425-0.85005 0.64801-1.2708-0.18935-0.4334-0.39722-0.8583-0.59246-1.2892 0.18514-0.01094 0.37037-0.02181 0.55551-0.03191 0.12539 0.32905 0.26248 0.6538 0.36599 0.99126 0.11108-0.35766 0.27687-0.69422 0.41909-1.0401 0.19019-0.01346 0.38127-0.02524 0.5723-0.03449z" enable-background="new"/>
+  <path d="m29.825 6.0898v0.52916h0.79373v0.52916h-0.79373v0.26458h0.79373v0.53006h-0.79373v0.26367h0.79373v0.52916h-0.79373v0.26458h0.79373v0.52916h-0.79373v0.26458h0.79373v0.52916h-0.79373v0.26458h0.79373v0.52916h-0.79373v0.52916h2.6458v-5.5561zm1.0583 0.52916h1.0583v0.52916h-1.0583zm0 0.79374h1.0583v0.53006h-1.0583zm0 0.79373h1.0583v0.52916h-1.0583zm0 0.79373h1.0583v0.52916h-1.0583zm0 0.79374h1.0583v0.52916h-1.0583zm0 0.79373h1.0583v0.52916h-1.0583z" enable-background="new"/>
+ </g>
+</svg>

+ 16 - 0
web/img/application-vnd.ms-excel.template.macroenabled.12.8ee75839.svg

@@ -0,0 +1,16 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(1)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#3f755a" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <g transform="matrix(3.7796,0,0,3.7796,-77.229,-1.0169)" fill="#fff" stroke-width=".088193">
+  <path d="m29.199 5.0315c-1.2902 0.22976-2.5811 0.53459-3.8713 0.76014 0 1.9626-8.4e-4 3.9261 0 5.8896 1.2835 0.22471 2.5676 0.5245 3.8485 0.75847h0.38446v-7.4082zm-0.86117 2.3092c-0.22469 0.46033-0.45024 0.9207-0.67914 1.3793 0.23143 0.47127 0.46794 0.93908 0.7002 1.4104-0.20281-0.01178-0.40476-0.02445-0.60757-0.03876-0.14306-0.35093-0.31732-0.68998-0.41999-1.0569-0.11445 0.34167-0.27773 0.66395-0.40901 0.99889-0.1843-0.0025-0.36863-0.01013-0.55293-0.0177 0.21628-0.4233 0.425-0.85005 0.64801-1.2708-0.18935-0.4334-0.39722-0.8583-0.59246-1.2892 0.18514-0.01094 0.37037-0.02181 0.55551-0.03191 0.12539 0.32905 0.26248 0.6538 0.36599 0.99126 0.11108-0.35766 0.27687-0.69422 0.41909-1.0401 0.19019-0.01346 0.38127-0.02524 0.5723-0.03449z" enable-background="new"/>
+  <path d="m29.825 6.0898v0.52916h0.79373v0.52916h-0.79373v0.26458h0.79373v0.53006h-0.79373v0.26367h0.79373v0.52916h-0.79373v0.26458h0.79373v0.52916h-0.79373v0.26458h0.79373v0.52916h-0.79373v0.26458h0.79373v0.52916h-0.79373v0.52916h2.6458v-5.5561zm1.0583 0.52916h1.0583v0.52916h-1.0583zm0 0.79374h1.0583v0.53006h-1.0583zm0 0.79373h1.0583v0.52916h-1.0583zm0 0.79373h1.0583v0.52916h-1.0583zm0 0.79374h1.0583v0.52916h-1.0583zm0 0.79373h1.0583v0.52916h-1.0583z" enable-background="new"/>
+ </g>
+</svg>

+ 24 - 0
web/img/application-vnd.ms-htmlhelp.c8fb9bfd.svg

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <metadata>
+  <rdf:RDF>
+   <cc:Work rdf:about="">
+    <dc:format>image/svg+xml</dc:format>
+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+    <dc:title/>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#3fa7d7" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m8.4365 4.4978a3.9687 3.9687 0 0 0-3.9387 3.9687 3.9687 3.9687 0 0 0 3.9687 3.9687 3.9687 3.9687 0 0 0 3.9687-3.9687 3.9687 3.9687 0 0 0-3.9687-3.9687 3.9687 3.9687 0 0 0-0.029972 0zm0.027388 1.5875a2.3812 2.3812 0 0 1 0.00258 0 2.3812 2.3812 0 0 1 2.3812 2.3812 2.3812 2.3812 0 0 1-2.3812 2.3812 2.3812 2.3812 0 0 1-2.3812-2.3812 2.3812 2.3812 0 0 1 2.3786-2.3812z" fill="#ff6e65" stroke-linecap="round" stroke-linejoin="round" stroke-width=".61164" style="paint-order:stroke fill markers"/>
+ <path d="m4.862 6.821a3.948 3.948 0 0 0-0.364 1.645c0 0.587 0.134 1.144 0.364 1.646l1.389-0.794a2.378 2.378 0 0 1-0.166-0.852c0-0.3 0.064-0.587 0.166-0.851m-1.39-0.794m7.211 0-1.39 0.794c0.102 0.264 0.166 0.55 0.166 0.851 0 0.301-0.064 0.587-0.165 0.852l1.389 0.794c0.229-0.502 0.363-1.059 0.363-1.646 0-0.587-0.134-1.144-0.363-1.645m-4.457 3.862-0.794 1.388c0.502 0.23 1.059 0.364 1.646 0.364 0.587 0 1.144-0.134 1.645-0.364l-0.794-1.389c-0.264 0.102-0.55 0.166-0.851 0.166-0.301 0-0.587-0.064-0.852-0.166m0.852-6.184c-0.587 0-1.144 0.134-1.646 0.363l0.794 1.39c0.265-0.102 0.55-0.166 0.852-0.166 0.3 0 0.587 0.064 0.851 0.165l0.794-1.389a3.948 3.948 0 0 0-1.645-0.363" fill="#fff"/>
+</svg>

+ 13 - 0
web/img/application-vnd.ms-infopath.c0a76ad3.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(1)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#8e50c7" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m38 18-4 4.5-1.26 1.5h3.26v3h-12v13h-3.238l1.238 1.5 4 4.5 4-4.5 1.26-1.5h-3.26v-3h12v-13h3.238l-1.238-1.5zm-10 13h8v2h-8z" color="#4d4d4d" color-rendering="auto" fill="#fff" image-rendering="auto" opacity=".75" shape-rendering="auto" solid-color="#000000" style="isolation:auto;mix-blend-mode:normal"/>
+</svg>

+ 16 - 0
web/img/application-vnd.ms-powerpoint.b1a13336.svg

@@ -0,0 +1,16 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#e34e2b" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <g transform="translate(-17.672 1.3286)" fill="#fff" stroke-width=".088193">
+  <path d="m26.438 3.4338c-1.2902 0.22976-2.5811 0.53459-3.8713 0.76014 0 1.9626-8.5e-4 3.9261 0 5.8896 1.2835 0.22471 2.5676 0.5245 3.8485 0.75847h0.38446v-7.4082zm-1.9413 2.3328c0.25455-0.0054 0.4988 0.03123 0.69723 0.21071 0.38011 0.46125 0.27959 1.3096-0.28396 1.5857-0.2002 0.1023-0.42953 0.08832-0.64736 0.08126-8.75e-4 0.34483-0.0018 0.68958-9e-4 1.0344-0.17462-0.01499-0.35001-0.02994-0.52463-0.04405-0.0079-0.9419-0.0098-1.8411 7.75e-4 -2.783 0.24834-0.03094 0.50431-0.07957 0.75885-0.08497zm-0.02933 0.51107c-0.06946-0.0018-0.14052 0.0045-0.20567 0.0075-0.0026 0.29633-0.0043 0.59169 0.01421 0.88714 0.1755-0.02117 0.40304-0.0035 0.50358-0.18513 0.08378-0.17815 0.08904-0.40578-0.0115-0.57864-0.07331-0.10197-0.18485-0.12777-0.30062-0.13087z" enable-background="new"/>
+  <path d="m27.065 4.4921v0.87939c0.124-0.02662 0.25872-0.0767 0.39674-0.085v1.19h1.1827c-0.04202 0.36101-0.20295 0.73206-0.52128 0.92073-0.3122 0.2099-0.70894 0.2126-1.0582 0.10413 8.75e-4 0.17286-8.75e-4 0.52205 0 0.6949h2.1165v0.26458h-2.1165v0.52916h2.1165v0.26458h-2.1165v0.79374h2.6457v-5.5561zm0.66132 0.63406c0.56254 0.05826 1.0277 0.52454 1.0889 1.0857h-1.0889z" enable-background="new"/>
+ </g>
+</svg>

+ 16 - 0
web/img/application-vnd.ms-powerpoint.template.macroenabled.12.0409b2fe.svg

@@ -0,0 +1,16 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(1)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#813d17" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <g transform="matrix(3.7796 0 0 3.7796 -66.793 5.0216)" fill="#fff" stroke-width=".088193">
+  <path d="m26.438 3.4338c-1.2902 0.22976-2.5811 0.53459-3.8713 0.76014 0 1.9626-8.5e-4 3.9261 0 5.8896 1.2835 0.22471 2.5676 0.5245 3.8485 0.75847h0.38446v-7.4082zm-1.9413 2.3328c0.25455-0.0054 0.4988 0.03123 0.69723 0.21071 0.38011 0.46125 0.27959 1.3096-0.28396 1.5857-0.2002 0.1023-0.42953 0.08832-0.64736 0.08126-8.75e-4 0.34483-0.0018 0.68958-9e-4 1.0344-0.17462-0.01499-0.35001-0.02994-0.52463-0.04405-0.0079-0.9419-0.0098-1.8411 7.75e-4 -2.783 0.24834-0.03094 0.50431-0.07957 0.75885-0.08497zm-0.02933 0.51107c-0.06946-0.0018-0.14052 0.0045-0.20567 0.0075-0.0026 0.29633-0.0043 0.59169 0.01421 0.88714 0.1755-0.02117 0.40304-0.0035 0.50358-0.18513 0.08378-0.17815 0.08904-0.40578-0.0115-0.57864-0.07331-0.10197-0.18485-0.12777-0.30062-0.13087z" enable-background="new"/>
+  <path d="m27.065 4.4921v0.87939c0.124-0.02662 0.25872-0.0767 0.39674-0.085v1.19h1.1827c-0.04202 0.36101-0.20295 0.73206-0.52128 0.92073-0.3122 0.2099-0.70894 0.2126-1.0582 0.10413 8.75e-4 0.17286-8.75e-4 0.52205 0 0.6949h2.1165v0.26458h-2.1165v0.52916h2.1165v0.26458h-2.1165v0.79374h2.6457v-5.5561zm0.66132 0.63406c0.56254 0.05826 1.0277 0.52454 1.0889 1.0857h-1.0889z" enable-background="new"/>
+ </g>
+</svg>

+ 13 - 0
web/img/application-vnd.ms-publisher.32dbb1c3.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(1)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#56cc56" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m35 21c-0.554 0-1 0.446-1 1v2h-12c-0.554 0-1 0.446-1 1v17c0 0.554 0.446 1 1 1h17c0.554 0 1-0.446 1-1v-4h2c0.554 0 1-0.446 1-1v-15c0-0.554-0.446-1-1-1h-7zm0 1h7v15h-2v-12c0-0.554-0.446-1-1-1-1.556-0.611-4.061-0.985-4-2zm-12 5h15v4h-15v-4zm0 6h7v1h-7v-1zm8 0h7v1h-7v-1zm-8 3h7v1h-7v-1zm8 0h7v1h-7v-1zm-8 3h7v1h-7v-1zm8 0h7v1h-7v-1z" color="#000000" color-rendering="auto" fill="#fff" image-rendering="auto" opacity=".9" shape-rendering="auto" solid-color="#000000" stroke-width="2" style="isolation:auto;mix-blend-mode:normal"/>
+</svg>

+ 16 - 0
web/img/application-vnd.ms-word.0209c533.svg

@@ -0,0 +1,16 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#3374d4" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <g transform="translate(-20.002 1.3094)" fill="#fff" stroke-width=".088193">
+  <path d="m28.768 3.453c-1.2902 0.22976-2.5811 0.53461-3.8713 0.76016 0 1.9626-8.5e-4 3.9261 0 5.8896 1.2835 0.22471 2.5677 0.52447 3.8486 0.75844h0.38441v-7.4082zm-0.49905 2.3996-0.5944 2.4951-0.48788-0.03847c-0.11193-0.55714-0.24233-1.1109-0.34248-1.6706-0.09847 0.54368-0.2265 1.0823-0.33928 1.6226-0.16159-0.0084-0.32392-0.01852-0.48635-0.02946-0.13971-0.74062-0.30388-1.4762-0.43432-2.2185 0.14392-0.0067 0.28868-0.01258 0.4326-0.01763 0.08668 0.53611 0.18512 1.0697 0.26087 1.6066 0.11867-0.55041 0.23994-1.1008 0.35777-1.6512 0.15991-0.0093 0.31979-0.01599 0.47969-0.02441 0.11193 0.56809 0.22634 1.1352 0.34753 1.7008 0.0951-0.58408 0.20033-1.1664 0.30217-1.7496 0.16832-0.0059 0.33662-0.01517 0.5041-0.02527z" enable-background="new"/>
+  <path d="m29.395 4.5113h2.6458v5.2916h-2.6458v-0.52916h2.1166v-0.26458h-2.1166v-0.52916h2.1166v-0.26458h-2.1166v-0.52916h2.1166v-0.26458h-2.1166v-0.52916h2.1166v-0.26458h-2.1166v-0.52823h2.1166v-0.2655h-2.1166v-0.52916h2.1166v-0.26458h-2.1166z" enable-background="new"/>
+ </g>
+</svg>

+ 13 - 0
web/img/application-vnd.oasis.opendocument.chart.91c53f93.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <rect x="8" y="4" width="48" height="56" ry="5" fill="#f4f4f4" style="paint-order:stroke fill markers"/>
+ <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".1" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ <path d="m19 16h6v32h-6z" color="#bebebe" fill="#68b723" overflow="visible"/>
+ <path d="m29 32h6v16h-6z" fill="#c6262e" overflow="visible"/>
+ <path d="m39 24h6v24h-6z" color="#bebebe" fill="#3689e6" overflow="visible"/>
+</svg>

+ 13 - 0
web/img/application-vnd.oasis.opendocument.formula-template.d5dbf3d8.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(1)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#4d4d4d" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".35" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m40 24.505-8 4e-3 -4.441 7.691-1.555-2.695h-2v1h1.428l2.133 3.693 5.02-8.693h6.42v1h1zm-5 3c-1.662 0-3 1.338-3 3s1.338 3 3 3c0.773 0 1.469-0.298 2-0.775v0.775h1v-6h-1v0.775c-0.531-0.477-1.227-0.775-2-0.775m0 1c1.108 0 2 0.892 2 2s-0.892 2-2 2-2-0.892-2-2 0.892-2 2-2m-11.99 6.99c-6e-3 0-0.01 0.223-0.01 0.5s4e-3 0.5 0.01 0.5h17.98c6e-3 0 0.01-0.223 0.01-0.5s-4e-3 -0.5-0.01-0.5zm8 3c-6e-3 0-0.01 0.223-0.01 0.5s4e-3 0.5 0.01 0.5h1.98c6e-3 0 0.01-0.223 0.01-0.5s-4e-3 -0.5-0.01-0.5z" color="#000000" color-rendering="auto" fill="#afafaf" image-rendering="auto" shape-rendering="auto"/>
+</svg>

+ 13 - 0
web/img/application-vnd.oasis.opendocument.formula.a4bc6018.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(1)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#808080" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m40 24.505-8 4e-3 -4.441 7.691-1.555-2.695h-2v1h1.428l2.133 3.693 5.02-8.693h6.42v1h1zm-5 3c-1.662 0-3 1.338-3 3s1.338 3 3 3c0.773 0 1.469-0.298 2-0.775v0.775h1v-6h-1v0.775c-0.531-0.477-1.227-0.775-2-0.775m0 1c1.108 0 2 0.892 2 2s-0.892 2-2 2-2-0.892-2-2 0.892-2 2-2m-11.99 6.99c-6e-3 0-0.01 0.223-0.01 0.5s4e-3 0.5 0.01 0.5h17.98c6e-3 0 0.01-0.223 0.01-0.5s-4e-3 -0.5-0.01-0.5zm8 3c-6e-3 0-0.01 0.223-0.01 0.5s4e-3 0.5 0.01 0.5h1.98c6e-3 0 0.01-0.223 0.01-0.5s-4e-3 -0.5-0.01-0.5z" color="#000000" color-rendering="auto" fill="#fff" image-rendering="auto" opacity=".75" shape-rendering="auto"/>
+</svg>

+ 13 - 0
web/img/application-vnd.oasis.opendocument.presentation-template.5b2c8be4.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(1)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#84603e" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m24 28.667c-2.9454 0-5.333 2.3877-5.333 5.3334 0 2.9455 2.3878 5.3334 5.333 5.3334 2.9454 0 5.333-2.3877 5.333-5.3334h-5.333zm8.0003 1.333v2h13v-2zm0.14351 6.0001-0.14351 2h13v-2zm-12.143 7.9999v1.9999h19.999l5.07e-4 -1.9999z" color="#000000" enable-background="new" fill="#fff" stroke-width="3.7796" style="text-decoration-line:none;text-indent:0;text-transform:none"/>
+</svg>

+ 13 - 0
web/img/application-vnd.oasis.opendocument.spreadsheet-template.a3256fa8.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(1)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#396939" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m20 50h24v-20h-24zm2-18h4v4h-4zm6.0001 0h14v4h-14zm-6.0001 6.0001h4v4h-4zm6.0001 0h14v4h-14zm-6.0001 6.0001h4v4h-4zm6.0001 0h14v4h-14z" enable-background="new" fill="#fff" stroke-width="3.7796"/>
+</svg>

+ 13 - 0
web/img/application-vnd.oasis.opendocument.text-template.4e634bfe.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(1)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#35677f" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m17 48.999v-2h20v2zm0-6.0001v-2h28v2zm0-6.0001v-2h28v2zm0-6.0001v-2h28v2z" enable-background="new" fill="#fff" stroke-width="3.7796"/>
+</svg>

文件差异内容过多而无法显示
+ 11 - 0
web/img/application-vnd.oasis.opendocument.web-template.5d282765.svg


+ 13 - 0
web/img/application-vnd.snap.72d9809b.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(.26458)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#f4f4f4" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".1" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m4.4314 4.6853 4.3741 4.3741v-2.7971zm5.0362 1.5966 3.034 1.3638-0.6679-1.3638zm-0.39338 0.09999v2.5419l1.8243-1.7127zm-1.3036 2.0513-2.0213 3.8145 2.9321-2.9322z" enable-background="new" fill="#c8c4b7" stroke-width=".13229"/>
+</svg>

+ 13 - 0
web/img/application-vnd.visio.90d89058.svg

@@ -0,0 +1,13 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(1)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#3d58a5" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <path d="m22 21c-0.554 0-1 0.446-1 1v20c0 0.554 0.446 1 1 1h20c0.554 0 1-0.446 1-1v-20c0-0.554-0.446-1-1-1h-20zm15.5 2.5 3 2.9961-2.5 2.5v9h-3v2h-5v-2h-4v-9.0508c-1.1624-0.23728-1.998-1.2589-2-2.4453 0-1.3807 1.1193-2.5 2.5-2.5 1.1879 1.38e-4 2.2116 0.83613 2.4492 2h6.0508l2.5-2.5zm-8.5605 3.5c-0.1999 0.9805-0.96522 1.7474-1.9453 1.9492v8.0508h3v-2h5v2h2v-8l-2-2h-6.0488-0.005859z" fill="#fff" opacity=".75"/>
+</svg>

+ 16 - 0
web/img/application-x-ace.03fa0685.svg

@@ -0,0 +1,16 @@
+<svg width="64" height="64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="a" x1="49.571" x2="51.714" y1="52.714" y2="54.857" gradientTransform="matrix(2.3333,0,0,2.3333,-68.667,-72.001)" gradientUnits="userSpaceOnUse">
+   <stop offset="0"/>
+   <stop stop-opacity="0" offset="1"/>
+  </linearGradient>
+ </defs>
+ <g transform="scale(1)">
+  <rect x="8" y="4" width="48" height="56" ry="5" fill="#415aa2" style="paint-order:stroke fill markers"/>
+  <path d="m56 46-14 14h9c2.77 0 5-2.23 5-5z" fill="url(#a)" fill-rule="evenodd" opacity=".15" stroke-width="8.8191" style="paint-order:stroke fill markers"/>
+ </g>
+ <g transform="matrix(3.7796 0 0 3.7796 -101.04 4.424)" fill="#fff">
+  <path d="m34.671 7.0315v1.7198c0 0.07329 0.059 0.13229 0.13229 0.13229h0.79374c0.07329 0 0.13229-0.059 0.13229-0.13229v-1.7198zm0.26458 1.0583h0.52916v0.52916h-0.52916z" color="#000000"/>
+  <path d="m35.2-0.11215v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916zm0 0.52916h-0.52916v0.52916h0.52916zm0 0.52916v0.52916h0.52916v-0.52916z" color="#000000"/>
+ </g>
+</svg>

部分文件因为文件数量过多而无法显示