mirror of
https://github.com/IceWhaleTech/CasaOS.git
synced 2025-11-07 07:09:46 +00:00
Merge branch 'main' into socket_service
This commit is contained in:
commit
191e07fdfe
4
.github/workflows/push_test_server.yml
vendored
4
.github/workflows/push_test_server.yml
vendored
@ -35,9 +35,9 @@ jobs:
|
|||||||
run: echo ${{env.VERSION}}
|
run: echo ${{env.VERSION}}
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.19
|
go-version: 'stable'
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v2
|
uses: goreleaser/goreleaser-action@v2
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
LEGACY_WITHOUT_VERSION ${DOWNLOAD_DOMAIN}IceWhaleTech/CasaOS/releases/download/v0.3.6/linux-${ARCH}-casaos-migration-tool-v0.3.6.tar.gz
|
LEGACY_WITHOUT_VERSION ${DOWNLOAD_DOMAIN}IceWhaleTech/CasaOS/releases/download/v0.3.6/linux-${ARCH}-casaos-migration-tool-v0.3.6.tar.gz
|
||||||
v0.3.5 ${DOWNLOAD_DOMAIN}IceWhaleTech/CasaOS/releases/download/v0.3.6/linux-${ARCH}-casaos-migration-tool-v0.3.6.tar.gz
|
v0.3.5 ${DOWNLOAD_DOMAIN}IceWhaleTech/CasaOS/releases/download/v0.3.6/linux-${ARCH}-casaos-migration-tool-v0.3.6.tar.gz
|
||||||
v0.3.5.1 ${DOWNLOAD_DOMAIN}IceWhaleTech/CasaOS/releases/download/v0.3.6/linux-${ARCH}-casaos-migration-tool-v0.3.6.tar.gz
|
v0.3.5.1 ${DOWNLOAD_DOMAIN}IceWhaleTech/CasaOS/releases/download/v0.3.6/linux-${ARCH}-casaos-migration-tool-v0.3.6.tar.gz
|
||||||
|
v0.4.2 ${DOWNLOAD_DOMAIN}IceWhaleTech/CasaOS/releases/download/v0.4.3/linux-${ARCH}-casaos-migration-tool-v0.4.3.tar.gz
|
||||||
|
|||||||
@ -25,7 +25,7 @@ GetNetCard() {
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [ -d "/sys/devices/virtual/net" ] && [ -d "/sys/class/net" ]; then
|
if [ -d "/sys/devices/virtual/net" ] && [ -d "/sys/class/net" ]; then
|
||||||
ls /sys/class/net/ | grep -v "$(ls /sys/devices/virtual/net/)"
|
ls /sys/class/net/ | grep -v "$(ls /sys/devices/virtual/net/)" -w
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,8 +11,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
interfaces "github.com/IceWhaleTech/CasaOS-Common"
|
interfaces "github.com/IceWhaleTech/CasaOS-Common"
|
||||||
"github.com/IceWhaleTech/CasaOS-Common/utils/version"
|
"github.com/IceWhaleTech/CasaOS-Common/utils/version"
|
||||||
|
"github.com/IceWhaleTech/CasaOS/pkg/utils/command"
|
||||||
)
|
)
|
||||||
|
|
||||||
type migrationTool struct{}
|
type migrationTool struct{}
|
||||||
@ -31,15 +34,15 @@ func (u *migrationTool) IsMigrationNeeded() (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if minorVersion > 3 {
|
if minorVersion > 4 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if minorVersion == 3 && patchVersion > 5 {
|
if minorVersion == 4 && patchVersion != 2 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Info("Migration is needed for a CasaOS version 0.3.5 and older...")
|
_logger.Info("Migration is needed for a CasaOS version 0.4.2 ")
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +51,10 @@ func (u *migrationTool) PreMigrate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *migrationTool) Migrate() error {
|
func (u *migrationTool) Migrate() error {
|
||||||
|
_logger.Info("Migration is started for a CasaOS version 0.4.2 ")
|
||||||
|
command.OnlyExec("systemctl stop rclone.service")
|
||||||
|
os.Remove("/usr/lib/systemd/system/rclone.service")
|
||||||
|
command.OnlyExec("systemctl daemon-reload")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
package drivers
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "github.com/IceWhaleTech/CasaOS/drivers/dropbox"
|
|
||||||
_ "github.com/IceWhaleTech/CasaOS/drivers/google_drive"
|
|
||||||
)
|
|
||||||
|
|
||||||
// All do nothing,just for import
|
|
||||||
// same as _ import
|
|
||||||
func All() {
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
package base
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var NoRedirectClient *resty.Client
|
|
||||||
var RestyClient = NewRestyClient()
|
|
||||||
var HttpClient = &http.Client{}
|
|
||||||
var UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
|
||||||
var DefaultTimeout = time.Second * 30
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
NoRedirectClient = resty.New().SetRedirectPolicy(
|
|
||||||
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
|
|
||||||
return http.ErrUseLastResponse
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
NoRedirectClient.SetHeader("user-agent", UserAgent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRestyClient() *resty.Client {
|
|
||||||
return resty.New().
|
|
||||||
SetHeader("user-agent", UserAgent).
|
|
||||||
SetRetryCount(3).
|
|
||||||
SetTimeout(DefaultTimeout)
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
package base
|
|
||||||
|
|
||||||
import "github.com/go-resty/resty/v2"
|
|
||||||
|
|
||||||
type Json map[string]interface{}
|
|
||||||
|
|
||||||
type TokenResp struct {
|
|
||||||
AccessToken string `json:"access_token"`
|
|
||||||
RefreshToken string `json:"refresh_token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReqCallback func(req *resty.Request)
|
|
||||||
@ -1,100 +0,0 @@
|
|||||||
package dropbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/driver"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/model"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Dropbox struct {
|
|
||||||
model.Storage
|
|
||||||
Addition
|
|
||||||
AccessToken string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dropbox) Config() driver.Config {
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dropbox) GetAddition() driver.Additional {
|
|
||||||
return &d.Addition
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dropbox) Init(ctx context.Context) error {
|
|
||||||
if len(d.RefreshToken) == 0 {
|
|
||||||
d.getRefreshToken()
|
|
||||||
}
|
|
||||||
return d.refreshToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dropbox) Drop(ctx context.Context) error {
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dropbox) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
|
||||||
files, err := d.getFiles(dir.GetID())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
|
|
||||||
return fileToObj(src), nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dropbox) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
|
||||||
url := "https://content.dropboxapi.com/2/files/download"
|
|
||||||
link := model.Link{
|
|
||||||
URL: url,
|
|
||||||
Method: http.MethodPost,
|
|
||||||
Header: http.Header{
|
|
||||||
"Authorization": []string{"Bearer " + d.AccessToken},
|
|
||||||
"Dropbox-API-Arg": []string{`{"path": "` + file.GetPath() + `"}`},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return &link, nil
|
|
||||||
}
|
|
||||||
func (d *Dropbox) GetUserInfo(ctx context.Context) (string, error) {
|
|
||||||
url := "https://api.dropboxapi.com/2/users/get_current_account"
|
|
||||||
user := UserInfo{}
|
|
||||||
resp, err := d.request(url, http.MethodPost, func(req *resty.Request) {
|
|
||||||
req.SetHeader("Content-Type", "")
|
|
||||||
}, &user)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
logger.Info("resp", zap.Any("resp", string(resp)))
|
|
||||||
return user.Email, nil
|
|
||||||
}
|
|
||||||
func (d *Dropbox) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dropbox) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dropbox) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dropbox) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
|
||||||
return errors.New("not support")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dropbox) Remove(ctx context.Context, obj model.Obj) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dropbox) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ driver.Driver = (*Dropbox)(nil)
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
package dropbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/driver"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/op"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ICONURL = "./img/driver/Dropbox.svg"
|
|
||||||
const APPKEY = "tciqajyazzdygt9"
|
|
||||||
const APPSECRET = "e7gtmv441cwdf0n"
|
|
||||||
|
|
||||||
type Addition struct {
|
|
||||||
driver.RootID
|
|
||||||
RefreshToken string `json:"refresh_token" required:"true" omit:"true"`
|
|
||||||
AppKey string `json:"app_key" type:"string" default:"tciqajyazzdygt9" omit:"true"`
|
|
||||||
AppSecret string `json:"app_secret" type:"string" default:"e7gtmv441cwdf0n" omit:"true"`
|
|
||||||
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" omit:"true"`
|
|
||||||
AuthUrl string `json:"auth_url" type:"string" default:"https://www.dropbox.com/oauth2/authorize?client_id=tciqajyazzdygt9&redirect_uri=https://cloudoauth.files.casaos.app&response_type=code&token_access_type=offline&state=${HOST}%2Fv1%2Frecover%2FDropbox&&force_reapprove=true&force_reauthentication=true"`
|
|
||||||
Icon string `json:"icon" type:"string" default:"./img/driver/Dropbox.svg"`
|
|
||||||
Code string `json:"code" type:"string" help:"code from auth_url" omit:"true"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = driver.Config{
|
|
||||||
Name: "Dropbox",
|
|
||||||
OnlyProxy: true,
|
|
||||||
DefaultRoot: "root",
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
op.RegisterDriver(func() driver.Driver {
|
|
||||||
return &Dropbox{}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,88 +0,0 @@
|
|||||||
package dropbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/model"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UserInfo struct {
|
|
||||||
AccountID string `json:"account_id"`
|
|
||||||
Name struct {
|
|
||||||
GivenName string `json:"given_name"`
|
|
||||||
Surname string `json:"surname"`
|
|
||||||
FamiliarName string `json:"familiar_name"`
|
|
||||||
DisplayName string `json:"display_name"`
|
|
||||||
AbbreviatedName string `json:"abbreviated_name"`
|
|
||||||
} `json:"name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
EmailVerified bool `json:"email_verified"`
|
|
||||||
Disabled bool `json:"disabled"`
|
|
||||||
Country string `json:"country"`
|
|
||||||
Locale string `json:"locale"`
|
|
||||||
ReferralLink string `json:"referral_link"`
|
|
||||||
IsPaired bool `json:"is_paired"`
|
|
||||||
AccountType struct {
|
|
||||||
Tag string `json:".tag"`
|
|
||||||
} `json:"account_type"`
|
|
||||||
RootInfo struct {
|
|
||||||
Tag string `json:".tag"`
|
|
||||||
RootNamespaceID string `json:"root_namespace_id"`
|
|
||||||
HomeNamespaceID string `json:"home_namespace_id"`
|
|
||||||
} `json:"root_info"`
|
|
||||||
}
|
|
||||||
type TokenError struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
ErrorDescription string `json:"error_description"`
|
|
||||||
}
|
|
||||||
type File struct {
|
|
||||||
Tag string `json:".tag"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
PathLower string `json:"path_lower"`
|
|
||||||
PathDisplay string `json:"path_display"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
ClientModified time.Time `json:"client_modified,omitempty"`
|
|
||||||
ServerModified time.Time `json:"server_modified,omitempty"`
|
|
||||||
Rev string `json:"rev,omitempty"`
|
|
||||||
Size int `json:"size,omitempty"`
|
|
||||||
IsDownloadable bool `json:"is_downloadable,omitempty"`
|
|
||||||
ContentHash string `json:"content_hash,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Files struct {
|
|
||||||
Files []File `json:"entries"`
|
|
||||||
Cursor string `json:"cursor"`
|
|
||||||
HasMore bool `json:"has_more"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Error struct {
|
|
||||||
Error struct {
|
|
||||||
Errors []struct {
|
|
||||||
Domain string `json:"domain"`
|
|
||||||
Reason string `json:"reason"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
LocationType string `json:"location_type"`
|
|
||||||
Location string `json:"location"`
|
|
||||||
}
|
|
||||||
Code int `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
} `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileToObj(f File) *model.ObjThumb {
|
|
||||||
logger.Info("dropbox file", zap.Any("file", f))
|
|
||||||
obj := &model.ObjThumb{
|
|
||||||
Object: model.Object{
|
|
||||||
ID: f.ID,
|
|
||||||
Name: f.Name,
|
|
||||||
Size: int64(f.Size),
|
|
||||||
Modified: f.ClientModified,
|
|
||||||
IsFolder: f.Tag == "folder",
|
|
||||||
Path: f.PathDisplay,
|
|
||||||
},
|
|
||||||
Thumbnail: model.Thumbnail{},
|
|
||||||
}
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
@ -1,102 +0,0 @@
|
|||||||
package dropbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/drivers/base"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (d *Dropbox) getRefreshToken() error {
|
|
||||||
url := "https://api.dropbox.com/oauth2/token"
|
|
||||||
var resp base.TokenResp
|
|
||||||
var e TokenError
|
|
||||||
|
|
||||||
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).
|
|
||||||
SetFormData(map[string]string{
|
|
||||||
"code": d.Code,
|
|
||||||
"grant_type": "authorization_code",
|
|
||||||
"redirect_uri": "https://cloudoauth.files.casaos.app",
|
|
||||||
}).SetBasicAuth(d.Addition.AppKey, d.Addition.AppSecret).SetHeader("Content-Type", "application/x-www-form-urlencoded").Post(url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Info("get refresh token", zap.String("res", res.String()))
|
|
||||||
if e.Error != "" {
|
|
||||||
return fmt.Errorf(e.Error)
|
|
||||||
}
|
|
||||||
d.RefreshToken = resp.RefreshToken
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
func (d *Dropbox) refreshToken() error {
|
|
||||||
url := "https://api.dropbox.com/oauth2/token"
|
|
||||||
var resp base.TokenResp
|
|
||||||
var e TokenError
|
|
||||||
|
|
||||||
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).
|
|
||||||
SetFormData(map[string]string{
|
|
||||||
"refresh_token": d.RefreshToken,
|
|
||||||
"grant_type": "refresh_token",
|
|
||||||
}).SetBasicAuth(d.Addition.AppKey, d.Addition.AppSecret).SetHeader("Content-Type", "application/x-www-form-urlencoded").Post(url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Info("get refresh token", zap.String("res", res.String()))
|
|
||||||
if e.Error != "" {
|
|
||||||
return fmt.Errorf(e.Error)
|
|
||||||
}
|
|
||||||
d.AccessToken = resp.AccessToken
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
func (d *Dropbox) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
|
||||||
req := base.RestyClient.R()
|
|
||||||
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
|
|
||||||
req.SetHeader("Content-Type", "application/json")
|
|
||||||
if callback != nil {
|
|
||||||
callback(req)
|
|
||||||
}
|
|
||||||
if resp != nil {
|
|
||||||
req.SetResult(resp)
|
|
||||||
}
|
|
||||||
var e Error
|
|
||||||
req.SetError(&e)
|
|
||||||
res, err := req.Execute(method, url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if e.Error.Code != 0 {
|
|
||||||
if e.Error.Code == 401 {
|
|
||||||
err = d.refreshToken()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return d.request(url, method, callback, resp)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
|
|
||||||
}
|
|
||||||
return res.Body(), nil
|
|
||||||
}
|
|
||||||
func (d *Dropbox) getFiles(path string) ([]File, error) {
|
|
||||||
|
|
||||||
res := make([]File, 0)
|
|
||||||
var resp Files
|
|
||||||
body := base.Json{
|
|
||||||
"limit": 2000,
|
|
||||||
"path": path,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := d.request("https://api.dropboxapi.com/2/files/list_folder", http.MethodPost, func(req *resty.Request) {
|
|
||||||
req.SetBody(body)
|
|
||||||
}, &resp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
res = append(res, resp.Files...)
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
@ -1,183 +0,0 @@
|
|||||||
package google_drive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/drivers/base"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/driver"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/model"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GoogleDrive struct {
|
|
||||||
model.Storage
|
|
||||||
Addition
|
|
||||||
AccessToken string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GoogleDrive) Config() driver.Config {
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GoogleDrive) GetAddition() driver.Additional {
|
|
||||||
return &d.Addition
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GoogleDrive) Init(ctx context.Context) error {
|
|
||||||
if d.ChunkSize == 0 {
|
|
||||||
d.ChunkSize = 5
|
|
||||||
}
|
|
||||||
if len(d.RefreshToken) == 0 {
|
|
||||||
d.getRefreshToken()
|
|
||||||
}
|
|
||||||
return d.refreshToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GoogleDrive) Drop(ctx context.Context) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GoogleDrive) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
|
||||||
files, err := d.getFiles(dir.GetID())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
|
|
||||||
return fileToObj(src), nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GoogleDrive) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
|
||||||
url := fmt.Sprintf("https://www.googleapis.com/drive/v3/files/%s?includeItemsFromAllDrives=true&supportsAllDrives=true", file.GetID())
|
|
||||||
_, err := d.request(url, http.MethodGet, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
link := model.Link{
|
|
||||||
Method: http.MethodGet,
|
|
||||||
URL: url + "&alt=media",
|
|
||||||
Header: http.Header{
|
|
||||||
"Authorization": []string{"Bearer " + d.AccessToken},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return &link, nil
|
|
||||||
}
|
|
||||||
func (d *GoogleDrive) GetUserInfo(ctx context.Context) (string, error) {
|
|
||||||
url := "https://content.googleapis.com/drive/v3/about?fields=user"
|
|
||||||
user := UserInfo{}
|
|
||||||
resp, err := d.request(url, http.MethodGet, nil, &user)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
logger.Info("resp", zap.Any("resp", resp))
|
|
||||||
return user.User.EmailAddress, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GoogleDrive) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
|
||||||
data := base.Json{
|
|
||||||
"name": dirName,
|
|
||||||
"parents": []string{parentDir.GetID()},
|
|
||||||
"mimeType": "application/vnd.google-apps.folder",
|
|
||||||
}
|
|
||||||
_, err := d.request("https://www.googleapis.com/drive/v3/files", http.MethodPost, func(req *resty.Request) {
|
|
||||||
req.SetBody(data)
|
|
||||||
}, nil)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GoogleDrive) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
|
||||||
query := map[string]string{
|
|
||||||
"addParents": dstDir.GetID(),
|
|
||||||
"removeParents": "root",
|
|
||||||
}
|
|
||||||
url := "https://www.googleapis.com/drive/v3/files/" + srcObj.GetID()
|
|
||||||
_, err := d.request(url, http.MethodPatch, func(req *resty.Request) {
|
|
||||||
req.SetQueryParams(query)
|
|
||||||
}, nil)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GoogleDrive) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
|
||||||
data := base.Json{
|
|
||||||
"name": newName,
|
|
||||||
}
|
|
||||||
url := "https://www.googleapis.com/drive/v3/files/" + srcObj.GetID()
|
|
||||||
_, err := d.request(url, http.MethodPatch, func(req *resty.Request) {
|
|
||||||
req.SetBody(data)
|
|
||||||
}, nil)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GoogleDrive) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
|
||||||
return errors.New("not support")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GoogleDrive) Remove(ctx context.Context, obj model.Obj) error {
|
|
||||||
url := "https://www.googleapis.com/drive/v3/files/" + obj.GetID()
|
|
||||||
_, err := d.request(url, http.MethodDelete, nil, nil)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GoogleDrive) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
|
||||||
obj := stream.GetOld()
|
|
||||||
var (
|
|
||||||
e Error
|
|
||||||
url string
|
|
||||||
data base.Json
|
|
||||||
res *resty.Response
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if obj != nil {
|
|
||||||
url = fmt.Sprintf("https://www.googleapis.com/upload/drive/v3/files/%s?uploadType=resumable&supportsAllDrives=true", obj.GetID())
|
|
||||||
data = base.Json{}
|
|
||||||
} else {
|
|
||||||
data = base.Json{
|
|
||||||
"name": stream.GetName(),
|
|
||||||
"parents": []string{dstDir.GetID()},
|
|
||||||
}
|
|
||||||
url = "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable&supportsAllDrives=true"
|
|
||||||
}
|
|
||||||
req := base.NoRedirectClient.R().
|
|
||||||
SetHeaders(map[string]string{
|
|
||||||
"Authorization": "Bearer " + d.AccessToken,
|
|
||||||
"X-Upload-Content-Type": stream.GetMimetype(),
|
|
||||||
"X-Upload-Content-Length": strconv.FormatInt(stream.GetSize(), 10),
|
|
||||||
}).
|
|
||||||
SetError(&e).SetBody(data).SetContext(ctx)
|
|
||||||
if obj != nil {
|
|
||||||
res, err = req.Patch(url)
|
|
||||||
} else {
|
|
||||||
res, err = req.Post(url)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if e.Error.Code != 0 {
|
|
||||||
if e.Error.Code == 401 {
|
|
||||||
err = d.refreshToken()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return d.Put(ctx, dstDir, stream, up)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
|
|
||||||
}
|
|
||||||
putUrl := res.Header().Get("location")
|
|
||||||
if stream.GetSize() < d.ChunkSize*1024*1024 {
|
|
||||||
_, err = d.request(putUrl, http.MethodPut, func(req *resty.Request) {
|
|
||||||
req.SetHeader("Content-Length", strconv.FormatInt(stream.GetSize(), 10)).SetBody(stream.GetReadCloser())
|
|
||||||
}, nil)
|
|
||||||
} else {
|
|
||||||
err = d.chunkUpload(ctx, stream, putUrl)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ driver.Driver = (*GoogleDrive)(nil)
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
package google_drive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/driver"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/op"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ICONURL = "./img/driver/GoogleDrive.svg"
|
|
||||||
const CLIENTID = "921743327851-urr4f7jjfp4ts639evqb3i4m4qb4u4cc.apps.googleusercontent.com"
|
|
||||||
const CLIENTSECRET = "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"
|
|
||||||
|
|
||||||
type Addition struct {
|
|
||||||
driver.RootID
|
|
||||||
RefreshToken string `json:"refresh_token" required:"true" omit:"true"`
|
|
||||||
OrderBy string `json:"order_by" type:"string" help:"such as: folder,name,modifiedTime" omit:"true"`
|
|
||||||
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" omit:"true"`
|
|
||||||
ClientID string `json:"client_id" required:"true" default:"921743327851-urr4f7jjfp4ts639evqb3i4m4qb4u4cc.apps.googleusercontent.com" omit:"true"`
|
|
||||||
ClientSecret string `json:"client_secret" required:"true" default:"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" omit:"true"`
|
|
||||||
ChunkSize int64 `json:"chunk_size" type:"number" help:"chunk size while uploading (unit: MB)" omit:"true"`
|
|
||||||
AuthUrl string `json:"auth_url" type:"string" default:"https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?response_type=code&client_id=921743327851-urr4f7jjfp4ts639evqb3i4m4qb4u4cc.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fcloudoauth.files.casaos.app&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.file&access_type=offline&approval_prompt=force&state=${HOST}%2Fv1%2Frecover%2FGoogleDrive&service=lso&o2v=1&flowName=GeneralOAuthFlow"`
|
|
||||||
Icon string `json:"icon" type:"string" default:"./img/driver/GoogleDrive.svg"`
|
|
||||||
Code string `json:"code" type:"string" help:"code from auth_url" omit:"true"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = driver.Config{
|
|
||||||
Name: "GoogleDrive",
|
|
||||||
OnlyProxy: true,
|
|
||||||
DefaultRoot: "root",
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
op.RegisterDriver(func() driver.Driver {
|
|
||||||
return &GoogleDrive{}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
package google_drive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS/model"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UserInfo struct {
|
|
||||||
User struct {
|
|
||||||
Kind string `json:"kind"`
|
|
||||||
DisplayName string `json:"displayName"`
|
|
||||||
PhotoLink string `json:"photoLink"`
|
|
||||||
Me bool `json:"me"`
|
|
||||||
PermissionID string `json:"permissionId"`
|
|
||||||
EmailAddress string `json:"emailAddress"`
|
|
||||||
} `json:"user"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TokenError struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
ErrorDescription string `json:"error_description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Files struct {
|
|
||||||
NextPageToken string `json:"nextPageToken"`
|
|
||||||
Files []File `json:"files"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
MimeType string `json:"mimeType"`
|
|
||||||
ModifiedTime time.Time `json:"modifiedTime"`
|
|
||||||
Size string `json:"size"`
|
|
||||||
ThumbnailLink string `json:"thumbnailLink"`
|
|
||||||
ShortcutDetails struct {
|
|
||||||
TargetId string `json:"targetId"`
|
|
||||||
TargetMimeType string `json:"targetMimeType"`
|
|
||||||
} `json:"shortcutDetails"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileToObj(f File) *model.ObjThumb {
|
|
||||||
log.Debugf("google file: %+v", f)
|
|
||||||
size, _ := strconv.ParseInt(f.Size, 10, 64)
|
|
||||||
obj := &model.ObjThumb{
|
|
||||||
Object: model.Object{
|
|
||||||
ID: f.Id,
|
|
||||||
Name: f.Name,
|
|
||||||
Size: size,
|
|
||||||
Modified: f.ModifiedTime,
|
|
||||||
IsFolder: f.MimeType == "application/vnd.google-apps.folder",
|
|
||||||
},
|
|
||||||
Thumbnail: model.Thumbnail{},
|
|
||||||
}
|
|
||||||
if f.MimeType == "application/vnd.google-apps.shortcut" {
|
|
||||||
obj.ID = f.ShortcutDetails.TargetId
|
|
||||||
obj.IsFolder = f.ShortcutDetails.TargetMimeType == "application/vnd.google-apps.folder"
|
|
||||||
}
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
type Error struct {
|
|
||||||
Error struct {
|
|
||||||
Errors []struct {
|
|
||||||
Domain string `json:"domain"`
|
|
||||||
Reason string `json:"reason"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
LocationType string `json:"location_type"`
|
|
||||||
Location string `json:"location"`
|
|
||||||
}
|
|
||||||
Code int `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
} `json:"error"`
|
|
||||||
}
|
|
||||||
@ -1,152 +0,0 @@
|
|||||||
package google_drive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/drivers/base"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/model"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// do others that not defined in Driver interface
|
|
||||||
|
|
||||||
func (d *GoogleDrive) getRefreshToken() error {
|
|
||||||
url := "https://www.googleapis.com/oauth2/v4/token"
|
|
||||||
var resp base.TokenResp
|
|
||||||
var e TokenError
|
|
||||||
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).
|
|
||||||
SetFormData(map[string]string{
|
|
||||||
"client_id": d.ClientID,
|
|
||||||
"client_secret": d.ClientSecret,
|
|
||||||
"code": d.Code,
|
|
||||||
"grant_type": "authorization_code",
|
|
||||||
"redirect_uri": "https://cloudoauth.files.casaos.app",
|
|
||||||
}).Post(url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Info("get refresh token", zap.String("res", res.String()))
|
|
||||||
if e.Error != "" {
|
|
||||||
return fmt.Errorf(e.Error)
|
|
||||||
}
|
|
||||||
d.RefreshToken = resp.RefreshToken
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GoogleDrive) refreshToken() error {
|
|
||||||
url := "https://www.googleapis.com/oauth2/v4/token"
|
|
||||||
var resp base.TokenResp
|
|
||||||
var e TokenError
|
|
||||||
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).
|
|
||||||
SetFormData(map[string]string{
|
|
||||||
"client_id": d.ClientID,
|
|
||||||
"client_secret": d.ClientSecret,
|
|
||||||
"refresh_token": d.RefreshToken,
|
|
||||||
"grant_type": "refresh_token",
|
|
||||||
}).Post(url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debug(res.String())
|
|
||||||
if e.Error != "" {
|
|
||||||
return fmt.Errorf(e.Error)
|
|
||||||
}
|
|
||||||
d.AccessToken = resp.AccessToken
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GoogleDrive) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
|
||||||
req := base.RestyClient.R()
|
|
||||||
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
|
|
||||||
req.SetQueryParam("includeItemsFromAllDrives", "true")
|
|
||||||
req.SetQueryParam("supportsAllDrives", "true")
|
|
||||||
if callback != nil {
|
|
||||||
callback(req)
|
|
||||||
}
|
|
||||||
if resp != nil {
|
|
||||||
req.SetResult(resp)
|
|
||||||
}
|
|
||||||
var e Error
|
|
||||||
req.SetError(&e)
|
|
||||||
res, err := req.Execute(method, url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if e.Error.Code != 0 {
|
|
||||||
if e.Error.Code == 401 {
|
|
||||||
err = d.refreshToken()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return d.request(url, method, callback, resp)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
|
|
||||||
}
|
|
||||||
return res.Body(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GoogleDrive) getFiles(id string) ([]File, error) {
|
|
||||||
pageToken := "first"
|
|
||||||
res := make([]File, 0)
|
|
||||||
for pageToken != "" {
|
|
||||||
if pageToken == "first" {
|
|
||||||
pageToken = ""
|
|
||||||
}
|
|
||||||
var resp Files
|
|
||||||
orderBy := "folder,name,modifiedTime desc"
|
|
||||||
if d.OrderBy != "" {
|
|
||||||
orderBy = d.OrderBy + " " + d.OrderDirection
|
|
||||||
}
|
|
||||||
query := map[string]string{
|
|
||||||
"orderBy": orderBy,
|
|
||||||
"fields": "files(id,name,mimeType,size,modifiedTime,thumbnailLink,shortcutDetails),nextPageToken",
|
|
||||||
"pageSize": "1000",
|
|
||||||
"q": fmt.Sprintf("'%s' in parents and trashed = false", id),
|
|
||||||
//"includeItemsFromAllDrives": "true",
|
|
||||||
//"supportsAllDrives": "true",
|
|
||||||
"pageToken": pageToken,
|
|
||||||
}
|
|
||||||
_, err := d.request("https://www.googleapis.com/drive/v3/files", http.MethodGet, func(req *resty.Request) {
|
|
||||||
req.SetQueryParams(query)
|
|
||||||
}, &resp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pageToken = resp.NextPageToken
|
|
||||||
res = append(res, resp.Files...)
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GoogleDrive) chunkUpload(ctx context.Context, stream model.FileStreamer, url string) error {
|
|
||||||
var defaultChunkSize = d.ChunkSize * 1024 * 1024
|
|
||||||
var finish int64 = 0
|
|
||||||
for finish < stream.GetSize() {
|
|
||||||
if utils.IsCanceled(ctx) {
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
chunkSize := stream.GetSize() - finish
|
|
||||||
if chunkSize > defaultChunkSize {
|
|
||||||
chunkSize = defaultChunkSize
|
|
||||||
}
|
|
||||||
_, err := d.request(url, http.MethodPut, func(req *resty.Request) {
|
|
||||||
req.SetHeaders(map[string]string{
|
|
||||||
"Content-Length": strconv.FormatInt(chunkSize, 10),
|
|
||||||
"Content-Range": fmt.Sprintf("bytes %d-%d/%d", finish, finish+chunkSize-1, stream.GetSize()),
|
|
||||||
}).SetBody(io.LimitReader(stream.GetReadCloser(), chunkSize)).SetContext(ctx)
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
finish += chunkSize
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
9
go.mod
9
go.mod
@ -5,14 +5,12 @@ go 1.19
|
|||||||
require (
|
require (
|
||||||
github.com/Curtis-Milo/nat-type-identifier-go v0.0.0-20220215191915-18d42168c63d
|
github.com/Curtis-Milo/nat-type-identifier-go v0.0.0-20220215191915-18d42168c63d
|
||||||
github.com/IceWhaleTech/CasaOS-Common v0.4.2-alpha3
|
github.com/IceWhaleTech/CasaOS-Common v0.4.2-alpha3
|
||||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
|
|
||||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||||
github.com/deckarep/golang-set/v2 v2.1.0
|
github.com/deckarep/golang-set/v2 v2.1.0
|
||||||
github.com/deepmap/oapi-codegen v1.12.4
|
github.com/deepmap/oapi-codegen v1.12.4
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/docker/docker v23.0.1+incompatible
|
github.com/docker/docker v23.0.1+incompatible
|
||||||
github.com/dsoprea/go-exif/v3 v3.0.0-20221012082141-d21ac8e2de85
|
github.com/dsoprea/go-exif/v3 v3.0.0-20221012082141-d21ac8e2de85
|
||||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd
|
|
||||||
github.com/getkin/kin-openapi v0.113.0
|
github.com/getkin/kin-openapi v0.113.0
|
||||||
github.com/gin-contrib/gzip v0.0.6
|
github.com/gin-contrib/gzip v0.0.6
|
||||||
github.com/gin-gonic/gin v1.8.2
|
github.com/gin-gonic/gin v1.8.2
|
||||||
@ -26,7 +24,6 @@ require (
|
|||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/h2non/filetype v1.1.3
|
github.com/h2non/filetype v1.1.3
|
||||||
github.com/hirochachacha/go-smb2 v1.1.0
|
github.com/hirochachacha/go-smb2 v1.1.0
|
||||||
github.com/json-iterator/go v1.1.12
|
|
||||||
github.com/labstack/echo/v4 v4.10.0
|
github.com/labstack/echo/v4 v4.10.0
|
||||||
github.com/maruel/natural v1.1.0
|
github.com/maruel/natural v1.1.0
|
||||||
github.com/mholt/archiver/v3 v3.5.1
|
github.com/mholt/archiver/v3 v3.5.1
|
||||||
@ -39,7 +36,6 @@ require (
|
|||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/satori/go.uuid v1.2.0
|
github.com/satori/go.uuid v1.2.0
|
||||||
github.com/shirou/gopsutil/v3 v3.22.11
|
github.com/shirou/gopsutil/v3 v3.22.11
|
||||||
github.com/sirupsen/logrus v1.9.0
|
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.1
|
||||||
github.com/tidwall/gjson v1.14.4
|
github.com/tidwall/gjson v1.14.4
|
||||||
go.uber.org/zap v1.24.0
|
go.uber.org/zap v1.24.0
|
||||||
@ -57,6 +53,7 @@ require (
|
|||||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||||
|
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
|
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
|
||||||
github.com/geoffgarside/ber v1.1.0 // indirect
|
github.com/geoffgarside/ber v1.1.0 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
@ -77,13 +74,14 @@ require (
|
|||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/go-cmp v0.5.9 // indirect
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
github.com/google/go-querystring v1.0.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/gorilla/mux v1.8.0 // indirect
|
github.com/gorilla/mux v1.8.0 // indirect
|
||||||
github.com/invopop/yaml v0.1.0 // indirect
|
github.com/invopop/yaml v0.1.0 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.15.13 // indirect
|
github.com/klauspost/compress v1.15.13 // indirect
|
||||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
@ -104,6 +102,7 @@ require (
|
|||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.0 // indirect
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||||
|
|||||||
6
go.sum
6
go.sum
@ -4,8 +4,6 @@ github.com/Curtis-Milo/nat-type-identifier-go v0.0.0-20220215191915-18d42168c63d
|
|||||||
github.com/IceWhaleTech/CasaOS-Common v0.4.2-alpha3 h1:WJUYo+hJpLmza7mQngoJVeUJOfnrZevNrX5wzTuOJo0=
|
github.com/IceWhaleTech/CasaOS-Common v0.4.2-alpha3 h1:WJUYo+hJpLmza7mQngoJVeUJOfnrZevNrX5wzTuOJo0=
|
||||||
github.com/IceWhaleTech/CasaOS-Common v0.4.2-alpha3/go.mod h1:xcemiRsXcs1zrmQxYMyExDjZ7UHYwkJqYE71IDIV0xA=
|
github.com/IceWhaleTech/CasaOS-Common v0.4.2-alpha3/go.mod h1:xcemiRsXcs1zrmQxYMyExDjZ7UHYwkJqYE71IDIV0xA=
|
||||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a h1:RenIAa2q4H8UcS/cqmwdT1WCWIAH5aumP8m8RpbqVsE=
|
|
||||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a/go.mod h1:sSBbaOg90XwWKtpT56kVujF0bIeVITnPlssLclogS04=
|
|
||||||
github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
|
github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
|
||||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||||
@ -123,6 +121,7 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
|
|||||||
github.com/gomodule/redigo v1.8.4/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
github.com/gomodule/redigo v1.8.4/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
||||||
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
|
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
|
||||||
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
|
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
|
||||||
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
@ -130,8 +129,9 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
|||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-github/v36 v36.0.0 h1:ndCzM616/oijwufI7nBRa+5eZHLldT+4yIB68ib5ogs=
|
github.com/google/go-github/v36 v36.0.0 h1:ndCzM616/oijwufI7nBRa+5eZHLldT+4yIB68ib5ogs=
|
||||||
github.com/google/go-github/v36 v36.0.0/go.mod h1:LFlKC047IOqiglRGNqNb9s/iAPTnnjtlshm+bxp+kwk=
|
github.com/google/go-github/v36 v36.0.0/go.mod h1:LFlKC047IOqiglRGNqNb9s/iAPTnnjtlshm+bxp+kwk=
|
||||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
|
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
|||||||
@ -1,43 +0,0 @@
|
|||||||
package conf
|
|
||||||
|
|
||||||
type Database struct {
|
|
||||||
Type string `json:"type" env:"DB_TYPE"`
|
|
||||||
Host string `json:"host" env:"DB_HOST"`
|
|
||||||
Port int `json:"port" env:"DB_PORT"`
|
|
||||||
User string `json:"user" env:"DB_USER"`
|
|
||||||
Password string `json:"password" env:"DB_PASS"`
|
|
||||||
Name string `json:"name" env:"DB_NAME"`
|
|
||||||
DBFile string `json:"db_file" env:"DB_FILE"`
|
|
||||||
TablePrefix string `json:"table_prefix" env:"DB_TABLE_PREFIX"`
|
|
||||||
SSLMode string `json:"ssl_mode" env:"DB_SSL_MODE"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Scheme struct {
|
|
||||||
Https bool `json:"https" env:"HTTPS"`
|
|
||||||
CertFile string `json:"cert_file" env:"CERT_FILE"`
|
|
||||||
KeyFile string `json:"key_file" env:"KEY_FILE"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogConfig struct {
|
|
||||||
Enable bool `json:"enable" env:"LOG_ENABLE"`
|
|
||||||
Name string `json:"name" env:"LOG_NAME"`
|
|
||||||
MaxSize int `json:"max_size" env:"MAX_SIZE"`
|
|
||||||
MaxBackups int `json:"max_backups" env:"MAX_BACKUPS"`
|
|
||||||
MaxAge int `json:"max_age" env:"MAX_AGE"`
|
|
||||||
Compress bool `json:"compress" env:"COMPRESS"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Force bool `json:"force" env:"FORCE"`
|
|
||||||
Address string `json:"address" env:"ADDR"`
|
|
||||||
Port int `json:"port" env:"PORT"`
|
|
||||||
SiteURL string `json:"site_url" env:"SITE_URL"`
|
|
||||||
Cdn string `json:"cdn" env:"CDN"`
|
|
||||||
JwtSecret string `json:"jwt_secret" env:"JWT_SECRET"`
|
|
||||||
TokenExpiresIn int `json:"token_expires_in" env:"TOKEN_EXPIRES_IN"`
|
|
||||||
Database Database `json:"database"`
|
|
||||||
Scheme Scheme `json:"scheme"`
|
|
||||||
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
|
|
||||||
BleveDir string `json:"bleve_dir" env:"BLEVE_DIR"`
|
|
||||||
Log LogConfig `json:"log"`
|
|
||||||
}
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
package conf
|
|
||||||
|
|
||||||
const (
|
|
||||||
TypeString = "string"
|
|
||||||
TypeSelect = "select"
|
|
||||||
TypeBool = "bool"
|
|
||||||
TypeText = "text"
|
|
||||||
TypeNumber = "number"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// site
|
|
||||||
VERSION = "version"
|
|
||||||
ApiUrl = "api_url"
|
|
||||||
BasePath = "base_path"
|
|
||||||
SiteTitle = "site_title"
|
|
||||||
Announcement = "announcement"
|
|
||||||
AllowIndexed = "allow_indexed"
|
|
||||||
|
|
||||||
Logo = "logo"
|
|
||||||
Favicon = "favicon"
|
|
||||||
MainColor = "main_color"
|
|
||||||
|
|
||||||
// preview
|
|
||||||
TextTypes = "text_types"
|
|
||||||
AudioTypes = "audio_types"
|
|
||||||
VideoTypes = "video_types"
|
|
||||||
ImageTypes = "image_types"
|
|
||||||
ProxyTypes = "proxy_types"
|
|
||||||
ProxyIgnoreHeaders = "proxy_ignore_headers"
|
|
||||||
AudioAutoplay = "audio_autoplay"
|
|
||||||
VideoAutoplay = "video_autoplay"
|
|
||||||
|
|
||||||
// global
|
|
||||||
HideFiles = "hide_files"
|
|
||||||
CustomizeHead = "customize_head"
|
|
||||||
CustomizeBody = "customize_body"
|
|
||||||
LinkExpiration = "link_expiration"
|
|
||||||
SignAll = "sign_all"
|
|
||||||
PrivacyRegs = "privacy_regs"
|
|
||||||
OcrApi = "ocr_api"
|
|
||||||
FilenameCharMapping = "filename_char_mapping"
|
|
||||||
|
|
||||||
// index
|
|
||||||
SearchIndex = "search_index"
|
|
||||||
AutoUpdateIndex = "auto_update_index"
|
|
||||||
IndexPaths = "index_paths"
|
|
||||||
IgnorePaths = "ignore_paths"
|
|
||||||
|
|
||||||
// aria2
|
|
||||||
Aria2Uri = "aria2_uri"
|
|
||||||
Aria2Secret = "aria2_secret"
|
|
||||||
|
|
||||||
// single
|
|
||||||
Token = "token"
|
|
||||||
IndexProgress = "index_progress"
|
|
||||||
|
|
||||||
//Github
|
|
||||||
GithubClientId = "github_client_id"
|
|
||||||
GithubClientSecrets = "github_client_secrets"
|
|
||||||
GithubLoginEnabled = "github_login_enabled"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
UNKNOWN = iota
|
|
||||||
FOLDER
|
|
||||||
//OFFICE
|
|
||||||
VIDEO
|
|
||||||
AUDIO
|
|
||||||
TEXT
|
|
||||||
IMAGE
|
|
||||||
)
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
package conf
|
|
||||||
|
|
||||||
import "regexp"
|
|
||||||
|
|
||||||
var (
|
|
||||||
BuiltAt string
|
|
||||||
GoVersion string
|
|
||||||
GitAuthor string
|
|
||||||
GitCommit string
|
|
||||||
Version string = "dev"
|
|
||||||
WebVersion string
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
Conf *Config
|
|
||||||
)
|
|
||||||
|
|
||||||
var SlicesMap = make(map[string][]string)
|
|
||||||
var FilenameCharMap = make(map[string]string)
|
|
||||||
var PrivacyReg []*regexp.Regexp
|
|
||||||
|
|
||||||
var (
|
|
||||||
// StoragesLoaded loaded success if empty
|
|
||||||
StoragesLoaded = false
|
|
||||||
)
|
|
||||||
var (
|
|
||||||
RawIndexHtml string
|
|
||||||
ManageHtml string
|
|
||||||
IndexHtml string
|
|
||||||
)
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* @Author: a624669980@163.com a624669980@163.com
|
|
||||||
* @Date: 2022-12-13 11:05:05
|
|
||||||
* @LastEditors: a624669980@163.com a624669980@163.com
|
|
||||||
* @LastEditTime: 2022-12-13 11:05:13
|
|
||||||
* @FilePath: /drive/internal/driver/config.go
|
|
||||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
|
||||||
*/
|
|
||||||
package driver
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
LocalSort bool `json:"local_sort"`
|
|
||||||
OnlyLocal bool `json:"only_local"`
|
|
||||||
OnlyProxy bool `json:"only_proxy"`
|
|
||||||
NoCache bool `json:"no_cache"`
|
|
||||||
NoUpload bool `json:"no_upload"`
|
|
||||||
NeedMs bool `json:"need_ms"` // if need get message from user, such as validate code
|
|
||||||
DefaultRoot string `json:"default_root"`
|
|
||||||
CheckStatus bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Config) MustProxy() bool {
|
|
||||||
return c.OnlyProxy || c.OnlyLocal
|
|
||||||
}
|
|
||||||
@ -1,131 +0,0 @@
|
|||||||
package driver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Driver interface {
|
|
||||||
Meta
|
|
||||||
Reader
|
|
||||||
User
|
|
||||||
//Writer
|
|
||||||
//Other
|
|
||||||
}
|
|
||||||
|
|
||||||
type Meta interface {
|
|
||||||
Config() Config
|
|
||||||
// GetStorage just get raw storage, no need to implement, because model.Storage have implemented
|
|
||||||
GetStorage() *model.Storage
|
|
||||||
SetStorage(model.Storage)
|
|
||||||
// GetAddition Additional is used for unmarshal of JSON, so need return pointer
|
|
||||||
GetAddition() Additional
|
|
||||||
// Init If already initialized, drop first
|
|
||||||
Init(ctx context.Context) error
|
|
||||||
Drop(ctx context.Context) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Other interface {
|
|
||||||
Other(ctx context.Context, args model.OtherArgs) (interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Reader interface {
|
|
||||||
// List files in the path
|
|
||||||
// if identify files by path, need to set ID with path,like path.Join(dir.GetID(), obj.GetName())
|
|
||||||
// if identify files by id, need to set ID with corresponding id
|
|
||||||
List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error)
|
|
||||||
// Link get url/filepath/reader of file
|
|
||||||
Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error)
|
|
||||||
}
|
|
||||||
type User interface {
|
|
||||||
// GetRoot get root directory of user
|
|
||||||
GetUserInfo(ctx context.Context) (string, error)
|
|
||||||
}
|
|
||||||
type Getter interface {
|
|
||||||
GetRoot(ctx context.Context) (model.Obj, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
//type Writer interface {
|
|
||||||
// Mkdir
|
|
||||||
// Move
|
|
||||||
// Rename
|
|
||||||
// Copy
|
|
||||||
// Remove
|
|
||||||
// Put
|
|
||||||
//}
|
|
||||||
|
|
||||||
type Mkdir interface {
|
|
||||||
MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Move interface {
|
|
||||||
Move(ctx context.Context, srcObj, dstDir model.Obj) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Rename interface {
|
|
||||||
Rename(ctx context.Context, srcObj model.Obj, newName string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Copy interface {
|
|
||||||
Copy(ctx context.Context, srcObj, dstDir model.Obj) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Remove interface {
|
|
||||||
Remove(ctx context.Context, obj model.Obj) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Put interface {
|
|
||||||
Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up UpdateProgress) error
|
|
||||||
}
|
|
||||||
|
|
||||||
//type WriteResult interface {
|
|
||||||
// MkdirResult
|
|
||||||
// MoveResult
|
|
||||||
// RenameResult
|
|
||||||
// CopyResult
|
|
||||||
// PutResult
|
|
||||||
// Remove
|
|
||||||
//}
|
|
||||||
|
|
||||||
type MkdirResult interface {
|
|
||||||
MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type MoveResult interface {
|
|
||||||
Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type RenameResult interface {
|
|
||||||
Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CopyResult interface {
|
|
||||||
Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type PutResult interface {
|
|
||||||
Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up UpdateProgress) (model.Obj, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateProgress func(percentage int)
|
|
||||||
|
|
||||||
type Progress struct {
|
|
||||||
Total int64
|
|
||||||
Done int64
|
|
||||||
up UpdateProgress
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Progress) Write(b []byte) (n int, err error) {
|
|
||||||
n = len(b)
|
|
||||||
p.Done += int64(n)
|
|
||||||
p.up(int(float64(p.Done) / float64(p.Total) * 100))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewProgress(total int64, up UpdateProgress) *Progress {
|
|
||||||
return &Progress{
|
|
||||||
Total: total,
|
|
||||||
up: up,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* @Author: a624669980@163.com a624669980@163.com
|
|
||||||
* @Date: 2022-12-13 11:05:47
|
|
||||||
* @LastEditors: a624669980@163.com a624669980@163.com
|
|
||||||
* @LastEditTime: 2022-12-13 11:05:54
|
|
||||||
* @FilePath: /drive/internal/driver/item.go
|
|
||||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
|
||||||
*/
|
|
||||||
package driver
|
|
||||||
|
|
||||||
type Additional interface{}
|
|
||||||
|
|
||||||
type Select string
|
|
||||||
|
|
||||||
type Item struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Default string `json:"default"`
|
|
||||||
Options string `json:"options"`
|
|
||||||
Required bool `json:"required"`
|
|
||||||
Help string `json:"help"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Info struct {
|
|
||||||
Common []Item `json:"common"`
|
|
||||||
Additional []Item `json:"additional"`
|
|
||||||
Config Config `json:"config"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type IRootPath interface {
|
|
||||||
GetRootPath() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type IRootId interface {
|
|
||||||
GetRootId() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type RootPath struct {
|
|
||||||
RootFolderPath string `json:"root_folder_path"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RootID struct {
|
|
||||||
RootFolderID string `json:"root_folder_id" omit:"true"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r RootPath) GetRootPath() string {
|
|
||||||
return r.RootFolderPath
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RootPath) SetRootPath(path string) {
|
|
||||||
r.RootFolderPath = path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r RootID) GetRootId() string {
|
|
||||||
return r.RootFolderID
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
package op
|
|
||||||
|
|
||||||
const (
|
|
||||||
WORK = "work"
|
|
||||||
RootName = "root"
|
|
||||||
)
|
|
||||||
@ -1,173 +0,0 @@
|
|||||||
package op
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/conf"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/driver"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type New func() driver.Driver
|
|
||||||
|
|
||||||
var driverNewMap = map[string]New{}
|
|
||||||
var driverInfoMap = map[string][]driver.Item{} //driver.Info{}
|
|
||||||
|
|
||||||
func RegisterDriver(driver New) {
|
|
||||||
// log.Infof("register driver: [%s]", config.Name)
|
|
||||||
tempDriver := driver()
|
|
||||||
tempConfig := tempDriver.Config()
|
|
||||||
registerDriverItems(tempConfig, tempDriver.GetAddition())
|
|
||||||
driverNewMap[tempConfig.Name] = driver
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDriverNew(name string) (New, error) {
|
|
||||||
n, ok := driverNewMap[name]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.Errorf("no driver named: %s", name)
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDriverNames() []string {
|
|
||||||
var driverNames []string
|
|
||||||
for k := range driverInfoMap {
|
|
||||||
driverNames = append(driverNames, k)
|
|
||||||
}
|
|
||||||
return driverNames
|
|
||||||
}
|
|
||||||
|
|
||||||
// func GetDriverInfoMap() map[string]driver.Info {
|
|
||||||
// return driverInfoMap
|
|
||||||
// }
|
|
||||||
func GetDriverInfoMap() map[string][]driver.Item {
|
|
||||||
return driverInfoMap
|
|
||||||
}
|
|
||||||
func registerDriverItems(config driver.Config, addition driver.Additional) {
|
|
||||||
// log.Debugf("addition of %s: %+v", config.Name, addition)
|
|
||||||
tAddition := reflect.TypeOf(addition)
|
|
||||||
for tAddition.Kind() == reflect.Pointer {
|
|
||||||
tAddition = tAddition.Elem()
|
|
||||||
}
|
|
||||||
//mainItems := getMainItems(config)
|
|
||||||
additionalItems := getAdditionalItems(tAddition, config.DefaultRoot)
|
|
||||||
driverInfoMap[config.Name] = additionalItems
|
|
||||||
// driver.Info{
|
|
||||||
// Common: mainItems,
|
|
||||||
// Additional: additionalItems,
|
|
||||||
// Config: config,
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMainItems(config driver.Config) []driver.Item {
|
|
||||||
items := []driver.Item{{
|
|
||||||
Name: "mount_path",
|
|
||||||
Type: conf.TypeString,
|
|
||||||
Required: true,
|
|
||||||
Help: "",
|
|
||||||
}, {
|
|
||||||
Name: "order",
|
|
||||||
Type: conf.TypeNumber,
|
|
||||||
Help: "use to sort",
|
|
||||||
}, {
|
|
||||||
Name: "remark",
|
|
||||||
Type: conf.TypeText,
|
|
||||||
}}
|
|
||||||
if !config.NoCache {
|
|
||||||
items = append(items, driver.Item{
|
|
||||||
Name: "cache_expiration",
|
|
||||||
Type: conf.TypeNumber,
|
|
||||||
Default: "30",
|
|
||||||
Required: true,
|
|
||||||
Help: "The cache expiration time for this storage",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if !config.OnlyProxy && !config.OnlyLocal {
|
|
||||||
items = append(items, []driver.Item{{
|
|
||||||
Name: "web_proxy",
|
|
||||||
Type: conf.TypeBool,
|
|
||||||
}, {
|
|
||||||
Name: "webdav_policy",
|
|
||||||
Type: conf.TypeSelect,
|
|
||||||
Options: "302_redirect,use_proxy_url,native_proxy",
|
|
||||||
Default: "302_redirect",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
}...)
|
|
||||||
} else {
|
|
||||||
items = append(items, driver.Item{
|
|
||||||
Name: "webdav_policy",
|
|
||||||
Type: conf.TypeSelect,
|
|
||||||
Default: "native_proxy",
|
|
||||||
Options: "use_proxy_url,native_proxy",
|
|
||||||
Required: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
items = append(items, driver.Item{
|
|
||||||
Name: "down_proxy_url",
|
|
||||||
Type: conf.TypeText,
|
|
||||||
})
|
|
||||||
if config.LocalSort {
|
|
||||||
items = append(items, []driver.Item{{
|
|
||||||
Name: "order_by",
|
|
||||||
Type: conf.TypeSelect,
|
|
||||||
Options: "name,size,modified",
|
|
||||||
}, {
|
|
||||||
Name: "order_direction",
|
|
||||||
Type: conf.TypeSelect,
|
|
||||||
Options: "asc,desc",
|
|
||||||
}}...)
|
|
||||||
}
|
|
||||||
items = append(items, driver.Item{
|
|
||||||
Name: "extract_folder",
|
|
||||||
Type: conf.TypeSelect,
|
|
||||||
Options: "front,back",
|
|
||||||
})
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAdditionalItems(t reflect.Type, defaultRoot string) []driver.Item {
|
|
||||||
var items []driver.Item
|
|
||||||
for i := 0; i < t.NumField(); i++ {
|
|
||||||
|
|
||||||
field := t.Field(i)
|
|
||||||
if field.Type.Kind() == reflect.Struct {
|
|
||||||
items = append(items, getAdditionalItems(field.Type, defaultRoot)...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tag := field.Tag
|
|
||||||
ignore, ok1 := tag.Lookup("ignore")
|
|
||||||
name, ok2 := tag.Lookup("json")
|
|
||||||
if (ok1 && ignore == "true") || !ok2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if tag.Get("omit") == "true" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
item := driver.Item{
|
|
||||||
Name: name,
|
|
||||||
Type: strings.ToLower(field.Type.Name()),
|
|
||||||
Default: tag.Get("default"),
|
|
||||||
Options: tag.Get("options"),
|
|
||||||
Required: tag.Get("required") == "true",
|
|
||||||
Help: tag.Get("help"),
|
|
||||||
}
|
|
||||||
if tag.Get("type") != "" {
|
|
||||||
item.Type = tag.Get("type")
|
|
||||||
}
|
|
||||||
if item.Name == "root_folder_id" || item.Name == "root_folder_path" {
|
|
||||||
if item.Default == "" {
|
|
||||||
item.Default = defaultRoot
|
|
||||||
}
|
|
||||||
item.Required = item.Default != ""
|
|
||||||
}
|
|
||||||
// set default type to string
|
|
||||||
if item.Type == "" {
|
|
||||||
item.Type = "string"
|
|
||||||
}
|
|
||||||
items = append(items, item)
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
@ -1,545 +0,0 @@
|
|||||||
package op
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
stdpath "path"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/driver"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/model"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/generic_sync"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/singleflight"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/utils"
|
|
||||||
"github.com/Xhofe/go-cache"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
pkgerr "github.com/pkg/errors"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// In order to facilitate adding some other things before and after file op
|
|
||||||
|
|
||||||
var listCache = cache.NewMemCache(cache.WithShards[[]model.Obj](64))
|
|
||||||
var listG singleflight.Group[[]model.Obj]
|
|
||||||
|
|
||||||
func updateCacheObj(storage driver.Driver, path string, oldObj model.Obj, newObj model.Obj) {
|
|
||||||
key := Key(storage, path)
|
|
||||||
objs, ok := listCache.Get(key)
|
|
||||||
if ok {
|
|
||||||
for i, obj := range objs {
|
|
||||||
if obj.GetName() == oldObj.GetName() {
|
|
||||||
objs[i] = newObj
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
listCache.Set(key, objs, cache.WithEx[[]model.Obj](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func delCacheObj(storage driver.Driver, path string, obj model.Obj) {
|
|
||||||
key := Key(storage, path)
|
|
||||||
objs, ok := listCache.Get(key)
|
|
||||||
if ok {
|
|
||||||
for i, oldObj := range objs {
|
|
||||||
if oldObj.GetName() == obj.GetName() {
|
|
||||||
objs = append(objs[:i], objs[i+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
listCache.Set(key, objs, cache.WithEx[[]model.Obj](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var addSortDebounceMap generic_sync.MapOf[string, func(func())]
|
|
||||||
|
|
||||||
func addCacheObj(storage driver.Driver, path string, newObj model.Obj) {
|
|
||||||
key := Key(storage, path)
|
|
||||||
objs, ok := listCache.Get(key)
|
|
||||||
if ok {
|
|
||||||
for i, obj := range objs {
|
|
||||||
if obj.GetName() == newObj.GetName() {
|
|
||||||
objs[i] = newObj
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple separation of files and folders
|
|
||||||
if len(objs) > 0 && objs[len(objs)-1].IsDir() == newObj.IsDir() {
|
|
||||||
objs = append(objs, newObj)
|
|
||||||
} else {
|
|
||||||
objs = append([]model.Obj{newObj}, objs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if storage.Config().LocalSort {
|
|
||||||
debounce, _ := addSortDebounceMap.LoadOrStore(key, utils.NewDebounce(time.Minute))
|
|
||||||
log.Debug("addCacheObj: wait start sort")
|
|
||||||
debounce(func() {
|
|
||||||
log.Debug("addCacheObj: start sort")
|
|
||||||
model.SortFiles(objs, storage.GetStorage().OrderBy, storage.GetStorage().OrderDirection)
|
|
||||||
addSortDebounceMap.Delete(key)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
listCache.Set(key, objs, cache.WithEx[[]model.Obj](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClearCache(storage driver.Driver, path string) {
|
|
||||||
listCache.Del(Key(storage, path))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Key(storage driver.Driver, path string) string {
|
|
||||||
return stdpath.Join(storage.GetStorage().MountPath, utils.FixAndCleanPath(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
// List files in storage, not contains virtual file
|
|
||||||
func List(ctx context.Context, storage driver.Driver, path string, args model.ListArgs, refresh ...bool) ([]model.Obj, error) {
|
|
||||||
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
|
|
||||||
return nil, errors.Errorf("storage not init: %s", storage.GetStorage().Status)
|
|
||||||
}
|
|
||||||
path = utils.FixAndCleanPath(path)
|
|
||||||
log.Debugf("op.List %s", path)
|
|
||||||
key := Key(storage, path)
|
|
||||||
if !utils.IsBool(refresh...) {
|
|
||||||
if files, ok := listCache.Get(key); ok {
|
|
||||||
log.Debugf("use cache when list %s", path)
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dir, err := GetUnwrap(ctx, storage, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "failed get dir")
|
|
||||||
}
|
|
||||||
log.Debugf("list dir: %+v", dir)
|
|
||||||
if !dir.IsDir() {
|
|
||||||
return nil, errors.WithStack(errors.New("not a folder"))
|
|
||||||
}
|
|
||||||
objs, err, _ := listG.Do(key, func() ([]model.Obj, error) {
|
|
||||||
files, err := storage.List(ctx, dir, args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "failed to list objs")
|
|
||||||
}
|
|
||||||
// set path
|
|
||||||
for _, f := range files {
|
|
||||||
if s, ok := f.(model.SetPath); ok && f.GetPath() == "" && dir.GetPath() != "" {
|
|
||||||
s.SetPath(stdpath.Join(dir.GetPath(), f.GetName()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// warp obj name
|
|
||||||
model.WrapObjsName(files)
|
|
||||||
// call hooks
|
|
||||||
go func(reqPath string, files []model.Obj) {
|
|
||||||
for _, hook := range ObjsUpdateHooks {
|
|
||||||
hook(args.ReqPath, files)
|
|
||||||
}
|
|
||||||
}(args.ReqPath, files)
|
|
||||||
|
|
||||||
// sort objs
|
|
||||||
if storage.Config().LocalSort {
|
|
||||||
model.SortFiles(files, storage.GetStorage().OrderBy, storage.GetStorage().OrderDirection)
|
|
||||||
}
|
|
||||||
model.ExtractFolder(files, storage.GetStorage().ExtractFolder)
|
|
||||||
|
|
||||||
if !storage.Config().NoCache {
|
|
||||||
if len(files) > 0 {
|
|
||||||
log.Debugf("set cache: %s => %+v", key, files)
|
|
||||||
listCache.Set(key, files, cache.WithEx[[]model.Obj](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
|
|
||||||
} else {
|
|
||||||
log.Debugf("del cache: %s", key)
|
|
||||||
listCache.Del(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
})
|
|
||||||
return objs, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get object from list of files
|
|
||||||
func Get(ctx context.Context, storage driver.Driver, path string) (model.Obj, error) {
|
|
||||||
path = utils.FixAndCleanPath(path)
|
|
||||||
log.Debugf("op.Get %s", path)
|
|
||||||
|
|
||||||
// is root folder
|
|
||||||
if utils.PathEqual(path, "/") {
|
|
||||||
var rootObj model.Obj
|
|
||||||
switch r := storage.GetAddition().(type) {
|
|
||||||
case driver.IRootId:
|
|
||||||
rootObj = &model.Object{
|
|
||||||
ID: r.GetRootId(),
|
|
||||||
Name: RootName,
|
|
||||||
Size: 0,
|
|
||||||
Modified: storage.GetStorage().Modified,
|
|
||||||
IsFolder: true,
|
|
||||||
Path: path,
|
|
||||||
}
|
|
||||||
case driver.IRootPath:
|
|
||||||
rootObj = &model.Object{
|
|
||||||
Path: r.GetRootPath(),
|
|
||||||
Name: RootName,
|
|
||||||
Size: 0,
|
|
||||||
Modified: storage.GetStorage().Modified,
|
|
||||||
IsFolder: true,
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if storage, ok := storage.(driver.Getter); ok {
|
|
||||||
obj, err := storage.GetRoot(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "failed get root obj")
|
|
||||||
}
|
|
||||||
rootObj = obj
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rootObj == nil {
|
|
||||||
return nil, errors.Errorf("please implement IRootPath or IRootId or Getter method")
|
|
||||||
}
|
|
||||||
return &model.ObjWrapName{
|
|
||||||
Name: RootName,
|
|
||||||
Obj: rootObj,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// not root folder
|
|
||||||
dir, name := stdpath.Split(path)
|
|
||||||
files, err := List(ctx, storage, dir, model.ListArgs{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "failed get parent list")
|
|
||||||
}
|
|
||||||
for _, f := range files {
|
|
||||||
// TODO maybe copy obj here
|
|
||||||
if f.GetName() == name {
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Debugf("cant find obj with name: %s", name)
|
|
||||||
return nil, errors.WithStack(errors.New("object not found"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUnwrap(ctx context.Context, storage driver.Driver, path string) (model.Obj, error) {
|
|
||||||
obj, err := Get(ctx, storage, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return model.UnwrapObjs(obj), err
|
|
||||||
}
|
|
||||||
|
|
||||||
var linkCache = cache.NewMemCache(cache.WithShards[*model.Link](16))
|
|
||||||
var linkG singleflight.Group[*model.Link]
|
|
||||||
|
|
||||||
// Link get link, if is an url. should have an expiry time
|
|
||||||
func Link(ctx context.Context, storage driver.Driver, path string, args model.LinkArgs) (*model.Link, model.Obj, error) {
|
|
||||||
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
|
|
||||||
return nil, nil, errors.Errorf("storage not init: %s", storage.GetStorage().Status)
|
|
||||||
}
|
|
||||||
file, err := GetUnwrap(ctx, storage, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.WithMessage(err, "failed to get file")
|
|
||||||
}
|
|
||||||
if file.IsDir() {
|
|
||||||
return nil, nil, errors.WithStack(errors.New("not a file"))
|
|
||||||
}
|
|
||||||
key := Key(storage, path) + ":" + args.IP
|
|
||||||
if link, ok := linkCache.Get(key); ok {
|
|
||||||
return link, file, nil
|
|
||||||
}
|
|
||||||
fn := func() (*model.Link, error) {
|
|
||||||
link, err := storage.Link(ctx, file, args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "failed get link")
|
|
||||||
}
|
|
||||||
if link.Expiration != nil {
|
|
||||||
linkCache.Set(key, link, cache.WithEx[*model.Link](*link.Expiration))
|
|
||||||
}
|
|
||||||
return link, nil
|
|
||||||
}
|
|
||||||
link, err, _ := linkG.Do(key, fn)
|
|
||||||
return link, file, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Other api
|
|
||||||
func Other(ctx context.Context, storage driver.Driver, args model.FsOtherArgs) (interface{}, error) {
|
|
||||||
obj, err := GetUnwrap(ctx, storage, args.Path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessagef(err, "failed to get obj")
|
|
||||||
}
|
|
||||||
if o, ok := storage.(driver.Other); ok {
|
|
||||||
return o.Other(ctx, model.OtherArgs{
|
|
||||||
Obj: obj,
|
|
||||||
Method: args.Method,
|
|
||||||
Data: args.Data,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("not implement")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var mkdirG singleflight.Group[interface{}]
|
|
||||||
|
|
||||||
func MakeDir(ctx context.Context, storage driver.Driver, path string, lazyCache ...bool) error {
|
|
||||||
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
|
|
||||||
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
|
|
||||||
}
|
|
||||||
path = utils.FixAndCleanPath(path)
|
|
||||||
key := Key(storage, path)
|
|
||||||
_, err, _ := mkdirG.Do(key, func() (interface{}, error) {
|
|
||||||
// check if dir exists
|
|
||||||
f, err := GetUnwrap(ctx, storage, path)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(pkgerr.Cause(err), errors.New("object not found")) {
|
|
||||||
parentPath, dirName := stdpath.Split(path)
|
|
||||||
err = MakeDir(ctx, storage, parentPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessagef(err, "failed to make parent dir [%s]", parentPath)
|
|
||||||
}
|
|
||||||
parentDir, err := GetUnwrap(ctx, storage, parentPath)
|
|
||||||
// this should not happen
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessagef(err, "failed to get parent dir [%s]", parentPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch s := storage.(type) {
|
|
||||||
case driver.MkdirResult:
|
|
||||||
var newObj model.Obj
|
|
||||||
newObj, err = s.MakeDir(ctx, parentDir, dirName)
|
|
||||||
if err == nil {
|
|
||||||
if newObj != nil {
|
|
||||||
addCacheObj(storage, parentPath, model.WrapObjName(newObj))
|
|
||||||
} else if !utils.IsBool(lazyCache...) {
|
|
||||||
ClearCache(storage, parentPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case driver.Mkdir:
|
|
||||||
err = s.MakeDir(ctx, parentDir, dirName)
|
|
||||||
if err == nil && !utils.IsBool(lazyCache...) {
|
|
||||||
ClearCache(storage, parentPath)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, errors.New("not implement")
|
|
||||||
}
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
return nil, errors.WithMessage(err, "failed to check if dir exists")
|
|
||||||
}
|
|
||||||
// dir exists
|
|
||||||
if f.IsDir() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
// dir to make is a file
|
|
||||||
return nil, errors.New("file exists")
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Move(ctx context.Context, storage driver.Driver, srcPath, dstDirPath string, lazyCache ...bool) error {
|
|
||||||
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
|
|
||||||
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
|
|
||||||
}
|
|
||||||
srcPath = utils.FixAndCleanPath(srcPath)
|
|
||||||
dstDirPath = utils.FixAndCleanPath(dstDirPath)
|
|
||||||
srcRawObj, err := Get(ctx, storage, srcPath)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithMessage(err, "failed to get src object")
|
|
||||||
}
|
|
||||||
srcObj := model.UnwrapObjs(srcRawObj)
|
|
||||||
dstDir, err := GetUnwrap(ctx, storage, dstDirPath)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithMessage(err, "failed to get dst dir")
|
|
||||||
}
|
|
||||||
srcDirPath := stdpath.Dir(srcPath)
|
|
||||||
|
|
||||||
switch s := storage.(type) {
|
|
||||||
case driver.MoveResult:
|
|
||||||
var newObj model.Obj
|
|
||||||
newObj, err = s.Move(ctx, srcObj, dstDir)
|
|
||||||
if err == nil {
|
|
||||||
delCacheObj(storage, srcDirPath, srcRawObj)
|
|
||||||
if newObj != nil {
|
|
||||||
addCacheObj(storage, dstDirPath, model.WrapObjName(newObj))
|
|
||||||
} else if !utils.IsBool(lazyCache...) {
|
|
||||||
ClearCache(storage, dstDirPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case driver.Move:
|
|
||||||
err = s.Move(ctx, srcObj, dstDir)
|
|
||||||
if err == nil {
|
|
||||||
delCacheObj(storage, srcDirPath, srcRawObj)
|
|
||||||
if !utils.IsBool(lazyCache...) {
|
|
||||||
ClearCache(storage, dstDirPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("not implement")
|
|
||||||
}
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Rename(ctx context.Context, storage driver.Driver, srcPath, dstName string, lazyCache ...bool) error {
|
|
||||||
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
|
|
||||||
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
|
|
||||||
}
|
|
||||||
srcPath = utils.FixAndCleanPath(srcPath)
|
|
||||||
srcRawObj, err := Get(ctx, storage, srcPath)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithMessage(err, "failed to get src object")
|
|
||||||
}
|
|
||||||
srcObj := model.UnwrapObjs(srcRawObj)
|
|
||||||
srcDirPath := stdpath.Dir(srcPath)
|
|
||||||
|
|
||||||
switch s := storage.(type) {
|
|
||||||
case driver.RenameResult:
|
|
||||||
var newObj model.Obj
|
|
||||||
newObj, err = s.Rename(ctx, srcObj, dstName)
|
|
||||||
if err == nil {
|
|
||||||
if newObj != nil {
|
|
||||||
updateCacheObj(storage, srcDirPath, srcRawObj, model.WrapObjName(newObj))
|
|
||||||
} else if !utils.IsBool(lazyCache...) {
|
|
||||||
ClearCache(storage, srcDirPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case driver.Rename:
|
|
||||||
err = s.Rename(ctx, srcObj, dstName)
|
|
||||||
if err == nil && !utils.IsBool(lazyCache...) {
|
|
||||||
ClearCache(storage, srcDirPath)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("not implement")
|
|
||||||
}
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy Just copy file[s] in a storage
|
|
||||||
func Copy(ctx context.Context, storage driver.Driver, srcPath, dstDirPath string, lazyCache ...bool) error {
|
|
||||||
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
|
|
||||||
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
|
|
||||||
}
|
|
||||||
srcPath = utils.FixAndCleanPath(srcPath)
|
|
||||||
dstDirPath = utils.FixAndCleanPath(dstDirPath)
|
|
||||||
srcObj, err := GetUnwrap(ctx, storage, srcPath)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithMessage(err, "failed to get src object")
|
|
||||||
}
|
|
||||||
dstDir, err := GetUnwrap(ctx, storage, dstDirPath)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithMessage(err, "failed to get dst dir")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch s := storage.(type) {
|
|
||||||
case driver.CopyResult:
|
|
||||||
var newObj model.Obj
|
|
||||||
newObj, err = s.Copy(ctx, srcObj, dstDir)
|
|
||||||
if err == nil {
|
|
||||||
if newObj != nil {
|
|
||||||
addCacheObj(storage, dstDirPath, model.WrapObjName(newObj))
|
|
||||||
} else if !utils.IsBool(lazyCache...) {
|
|
||||||
ClearCache(storage, dstDirPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case driver.Copy:
|
|
||||||
err = s.Copy(ctx, srcObj, dstDir)
|
|
||||||
if err == nil && !utils.IsBool(lazyCache...) {
|
|
||||||
ClearCache(storage, dstDirPath)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("not implement")
|
|
||||||
}
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Remove(ctx context.Context, storage driver.Driver, path string) error {
|
|
||||||
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
|
|
||||||
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
|
|
||||||
}
|
|
||||||
path = utils.FixAndCleanPath(path)
|
|
||||||
rawObj, err := Get(ctx, storage, path)
|
|
||||||
if err != nil {
|
|
||||||
// if object not found, it's ok
|
|
||||||
if errors.Is(pkgerr.Cause(err), errors.New("object not found")) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.WithMessage(err, "failed to get object")
|
|
||||||
}
|
|
||||||
dirPath := stdpath.Dir(path)
|
|
||||||
|
|
||||||
switch s := storage.(type) {
|
|
||||||
case driver.Remove:
|
|
||||||
err = s.Remove(ctx, model.UnwrapObjs(rawObj))
|
|
||||||
if err == nil {
|
|
||||||
delCacheObj(storage, dirPath, rawObj)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("not implement")
|
|
||||||
}
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Put(ctx context.Context, storage driver.Driver, dstDirPath string, file *model.FileStream, up driver.UpdateProgress, lazyCache ...bool) error {
|
|
||||||
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
|
|
||||||
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if f, ok := file.GetReadCloser().(*os.File); ok {
|
|
||||||
err := os.RemoveAll(f.Name())
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to remove file [%s]", f.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
defer func() {
|
|
||||||
if err := file.Close(); err != nil {
|
|
||||||
log.Errorf("failed to close file streamer, %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
// if file exist and size = 0, delete it
|
|
||||||
dstDirPath = utils.FixAndCleanPath(dstDirPath)
|
|
||||||
dstPath := stdpath.Join(dstDirPath, file.GetName())
|
|
||||||
fi, err := GetUnwrap(ctx, storage, dstPath)
|
|
||||||
if err == nil {
|
|
||||||
if fi.GetSize() == 0 {
|
|
||||||
err = Remove(ctx, storage, dstPath)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithMessagef(err, "failed remove file that exist and have size 0")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
file.Old = fi
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = MakeDir(ctx, storage, dstDirPath)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithMessagef(err, "failed to make dir [%s]", dstDirPath)
|
|
||||||
}
|
|
||||||
parentDir, err := GetUnwrap(ctx, storage, dstDirPath)
|
|
||||||
// this should not happen
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithMessagef(err, "failed to get dir [%s]", dstDirPath)
|
|
||||||
}
|
|
||||||
// if up is nil, set a default to prevent panic
|
|
||||||
if up == nil {
|
|
||||||
up = func(p int) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch s := storage.(type) {
|
|
||||||
case driver.PutResult:
|
|
||||||
var newObj model.Obj
|
|
||||||
newObj, err = s.Put(ctx, parentDir, file, up)
|
|
||||||
if err == nil {
|
|
||||||
if newObj != nil {
|
|
||||||
addCacheObj(storage, dstDirPath, model.WrapObjName(newObj))
|
|
||||||
} else if !utils.IsBool(lazyCache...) {
|
|
||||||
ClearCache(storage, dstDirPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case driver.Put:
|
|
||||||
err = s.Put(ctx, parentDir, file, up)
|
|
||||||
if err == nil && !utils.IsBool(lazyCache...) {
|
|
||||||
ClearCache(storage, dstDirPath)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("not implement")
|
|
||||||
}
|
|
||||||
log.Debugf("put file [%s] done", file.GetName())
|
|
||||||
//if err == nil {
|
|
||||||
// //clear cache
|
|
||||||
// key := stdpath.Join(storage.GetStorage().MountPath, dstDirPath)
|
|
||||||
// listCache.Del(key)
|
|
||||||
//}
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
@ -1,109 +0,0 @@
|
|||||||
package op
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/conf"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/driver"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/model"
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Obj
|
|
||||||
type ObjsUpdateHook = func(parent string, objs []model.Obj)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ObjsUpdateHooks = make([]ObjsUpdateHook, 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterObjsUpdateHook(hook ObjsUpdateHook) {
|
|
||||||
ObjsUpdateHooks = append(ObjsUpdateHooks, hook)
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleObjsUpdateHook(parent string, objs []model.Obj) {
|
|
||||||
for _, hook := range ObjsUpdateHooks {
|
|
||||||
hook(parent, objs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setting
|
|
||||||
type SettingItemHook func(item *model.SettingItem) error
|
|
||||||
|
|
||||||
var settingItemHooks = map[string]SettingItemHook{
|
|
||||||
conf.VideoTypes: func(item *model.SettingItem) error {
|
|
||||||
conf.SlicesMap[conf.VideoTypes] = strings.Split(item.Value, ",")
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
conf.AudioTypes: func(item *model.SettingItem) error {
|
|
||||||
conf.SlicesMap[conf.AudioTypes] = strings.Split(item.Value, ",")
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
conf.ImageTypes: func(item *model.SettingItem) error {
|
|
||||||
conf.SlicesMap[conf.ImageTypes] = strings.Split(item.Value, ",")
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
conf.TextTypes: func(item *model.SettingItem) error {
|
|
||||||
conf.SlicesMap[conf.TextTypes] = strings.Split(item.Value, ",")
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
conf.ProxyTypes: func(item *model.SettingItem) error {
|
|
||||||
conf.SlicesMap[conf.ProxyTypes] = strings.Split(item.Value, ",")
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
conf.ProxyIgnoreHeaders: func(item *model.SettingItem) error {
|
|
||||||
conf.SlicesMap[conf.ProxyIgnoreHeaders] = strings.Split(item.Value, ",")
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
conf.PrivacyRegs: func(item *model.SettingItem) error {
|
|
||||||
regStrs := strings.Split(item.Value, "\n")
|
|
||||||
regs := make([]*regexp.Regexp, 0, len(regStrs))
|
|
||||||
for _, regStr := range regStrs {
|
|
||||||
reg, err := regexp.Compile(regStr)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
regs = append(regs, reg)
|
|
||||||
}
|
|
||||||
conf.PrivacyReg = regs
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
conf.FilenameCharMapping: func(item *model.SettingItem) error {
|
|
||||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
|
||||||
err := json.UnmarshalFromString(item.Value, &conf.FilenameCharMap)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Info("filename char mapping", zap.Any("FilenameCharMap", conf.FilenameCharMap))
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterSettingItemHook(key string, hook SettingItemHook) {
|
|
||||||
settingItemHooks[key] = hook
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleSettingItemHook(item *model.SettingItem) (hasHook bool, err error) {
|
|
||||||
if hook, ok := settingItemHooks[item.Key]; ok {
|
|
||||||
return true, hook(item)
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Storage
|
|
||||||
type StorageHook func(typ string, storage driver.Driver)
|
|
||||||
|
|
||||||
var storageHooks = make([]StorageHook, 0)
|
|
||||||
|
|
||||||
func CallStorageHooks(typ string, storage driver.Driver) {
|
|
||||||
for _, hook := range storageHooks {
|
|
||||||
hook(typ, storage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterStorageHook(hook StorageHook) {
|
|
||||||
storageHooks = append(storageHooks, hook)
|
|
||||||
}
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
package sign
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/sign"
|
|
||||||
)
|
|
||||||
|
|
||||||
var once sync.Once
|
|
||||||
var instance sign.Sign
|
|
||||||
|
|
||||||
func Sign(data string) string {
|
|
||||||
|
|
||||||
return NotExpired(data)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithDuration(data string, d time.Duration) string {
|
|
||||||
once.Do(Instance)
|
|
||||||
return instance.Sign(data, time.Now().Add(d).Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
func NotExpired(data string) string {
|
|
||||||
once.Do(Instance)
|
|
||||||
return instance.Sign(data, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Verify(data string, sign string) error {
|
|
||||||
once.Do(Instance)
|
|
||||||
return instance.Verify(data, sign)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Instance() {
|
|
||||||
instance = sign.NewHMACSign([]byte("token"))
|
|
||||||
}
|
|
||||||
13
main.go
13
main.go
@ -30,7 +30,6 @@ import (
|
|||||||
"github.com/coreos/go-systemd/daemon"
|
"github.com/coreos/go-systemd/daemon"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
_ "github.com/IceWhaleTech/CasaOS/drivers"
|
|
||||||
"github.com/robfig/cron"
|
"github.com/robfig/cron"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@ -79,7 +78,7 @@ func init() {
|
|||||||
service.Cache = cache.Init()
|
service.Cache = cache.Init()
|
||||||
|
|
||||||
service.GetCPUThermalZone()
|
service.GetCPUThermalZone()
|
||||||
service.MyService.Storages().InitStorages()
|
|
||||||
route.InitFunction()
|
route.InitFunction()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +94,7 @@ func init() {
|
|||||||
// @name Authorization
|
// @name Authorization
|
||||||
// @BasePath /v1
|
// @BasePath /v1
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
if *versionFlag {
|
if *versionFlag {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -141,9 +141,9 @@ func main() {
|
|||||||
"/v1/image",
|
"/v1/image",
|
||||||
"/v1/samba",
|
"/v1/samba",
|
||||||
"/v1/notify",
|
"/v1/notify",
|
||||||
"/v1/driver",
|
//"/v1/driver",
|
||||||
"/v1/cloud",
|
//"/v1/cloud",
|
||||||
"/v1/recover",
|
//"/v1/recover",
|
||||||
"/v1/other",
|
"/v1/other",
|
||||||
route.V2APIPath,
|
route.V2APIPath,
|
||||||
route.V2DocPath,
|
route.V2DocPath,
|
||||||
@ -162,7 +162,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
var events []message_bus.EventType
|
var events []message_bus.EventType
|
||||||
events = append(events, message_bus.EventType{Name: "casaos:system:utilization", SourceID: common.SERVICENAME, PropertyTypeList: []message_bus.PropertyType{}})
|
events = append(events, message_bus.EventType{Name: "casaos:system:utilization", SourceID: common.SERVICENAME, PropertyTypeList: []message_bus.PropertyType{}})
|
||||||
events = append(events, message_bus.EventType{Name: "casaos:file:recover", SourceID: common.SERVICENAME, PropertyTypeList: []message_bus.PropertyType{}})
|
|
||||||
events = append(events, message_bus.EventType{Name: "casaos:file:operate", SourceID: common.SERVICENAME, PropertyTypeList: []message_bus.PropertyType{}})
|
events = append(events, message_bus.EventType{Name: "casaos:file:operate", SourceID: common.SERVICENAME, PropertyTypeList: []message_bus.PropertyType{}})
|
||||||
// register at message bus
|
// register at message bus
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
@ -225,7 +224,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("CasaOS main service is listening...", zap.Any("address", listener.Addr().String()))
|
logger.Info("CasaOS main service is listening...", zap.Any("address", listener.Addr().String()))
|
||||||
|
//defer service.MyService.Storage().UnmountAllStorage()
|
||||||
err = s.Serve(listener) // not using http.serve() to fix G114: Use of net/http serve function that has no support for setting timeouts (see https://github.com/securego/gosec)
|
err = s.Serve(listener) // not using http.serve() to fix G114: Use of net/http serve function that has no support for setting timeouts (see https://github.com/securego/gosec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|||||||
268
pkg/mount/dir.go
Normal file
268
pkg/mount/dir.go
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
package mount
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"bazil.org/fuse"
|
||||||
|
fusefs "bazil.org/fuse/fs"
|
||||||
|
"github.com/rclone/rclone/cmd/mountlib"
|
||||||
|
"github.com/rclone/rclone/fs"
|
||||||
|
"github.com/rclone/rclone/fs/log"
|
||||||
|
"github.com/rclone/rclone/vfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dir represents a directory entry
|
||||||
|
type Dir struct {
|
||||||
|
*vfs.Dir
|
||||||
|
fsys *FS
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check interface satisfied
|
||||||
|
var _ fusefs.Node = (*Dir)(nil)
|
||||||
|
|
||||||
|
// Attr updates the attributes of a directory
|
||||||
|
func (d *Dir) Attr(ctx context.Context, a *fuse.Attr) (err error) {
|
||||||
|
defer log.Trace(d, "")("attr=%+v, err=%v", a, &err)
|
||||||
|
a.Valid = d.fsys.opt.AttrTimeout
|
||||||
|
a.Gid = d.VFS().Opt.GID
|
||||||
|
a.Uid = d.VFS().Opt.UID
|
||||||
|
a.Mode = os.ModeDir | d.VFS().Opt.DirPerms
|
||||||
|
modTime := d.ModTime()
|
||||||
|
a.Atime = modTime
|
||||||
|
a.Mtime = modTime
|
||||||
|
a.Ctime = modTime
|
||||||
|
// FIXME include Valid so get some caching?
|
||||||
|
// FIXME fs.Debugf(d.path, "Dir.Attr %+v", a)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check interface satisfied
|
||||||
|
var _ fusefs.NodeSetattrer = (*Dir)(nil)
|
||||||
|
|
||||||
|
// Setattr handles attribute changes from FUSE. Currently supports ModTime only.
|
||||||
|
func (d *Dir) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) (err error) {
|
||||||
|
defer log.Trace(d, "stat=%+v", req)("err=%v", &err)
|
||||||
|
if d.VFS().Opt.NoModTime {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Valid.MtimeNow() {
|
||||||
|
err = d.SetModTime(time.Now())
|
||||||
|
} else if req.Valid.Mtime() {
|
||||||
|
err = d.SetModTime(req.Mtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check interface satisfied
|
||||||
|
var _ fusefs.NodeRequestLookuper = (*Dir)(nil)
|
||||||
|
|
||||||
|
// Lookup looks up a specific entry in the receiver.
|
||||||
|
//
|
||||||
|
// Lookup should return a Node corresponding to the entry. If the
|
||||||
|
// name does not exist in the directory, Lookup should return ENOENT.
|
||||||
|
//
|
||||||
|
// Lookup need not to handle the names "." and "..".
|
||||||
|
func (d *Dir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (node fusefs.Node, err error) {
|
||||||
|
defer log.Trace(d, "name=%q", req.Name)("node=%+v, err=%v", &node, &err)
|
||||||
|
mnode, err := d.Dir.Stat(req.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp.EntryValid = d.fsys.opt.AttrTimeout
|
||||||
|
// Check the mnode to see if it has a fuse Node cached
|
||||||
|
// We must return the same fuse nodes for vfs Nodes
|
||||||
|
node, ok := mnode.Sys().(fusefs.Node)
|
||||||
|
if ok {
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
|
switch x := mnode.(type) {
|
||||||
|
case *vfs.File:
|
||||||
|
node = &File{x, d.fsys}
|
||||||
|
case *vfs.Dir:
|
||||||
|
node = &Dir{x, d.fsys}
|
||||||
|
default:
|
||||||
|
panic("bad type")
|
||||||
|
}
|
||||||
|
// Cache the node for later
|
||||||
|
mnode.SetSys(node)
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check interface satisfied
|
||||||
|
var _ fusefs.HandleReadDirAller = (*Dir)(nil)
|
||||||
|
|
||||||
|
// ReadDirAll reads the contents of the directory
|
||||||
|
func (d *Dir) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error) {
|
||||||
|
itemsRead := -1
|
||||||
|
defer log.Trace(d, "")("item=%d, err=%v", &itemsRead, &err)
|
||||||
|
items, err := d.Dir.ReadDirAll()
|
||||||
|
if err != nil {
|
||||||
|
return nil, (err)
|
||||||
|
}
|
||||||
|
dirents = append(dirents, fuse.Dirent{
|
||||||
|
Type: fuse.DT_Dir,
|
||||||
|
Name: ".",
|
||||||
|
}, fuse.Dirent{
|
||||||
|
Type: fuse.DT_Dir,
|
||||||
|
Name: "..",
|
||||||
|
})
|
||||||
|
for _, node := range items {
|
||||||
|
name := node.Name()
|
||||||
|
if len(name) > mountlib.MaxLeafSize {
|
||||||
|
fs.Errorf(d, "Name too long (%d bytes) for FUSE, skipping: %s", len(name), name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var dirent = fuse.Dirent{
|
||||||
|
// Inode FIXME ???
|
||||||
|
Type: fuse.DT_File,
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
if node.IsDir() {
|
||||||
|
dirent.Type = fuse.DT_Dir
|
||||||
|
}
|
||||||
|
dirents = append(dirents, dirent)
|
||||||
|
}
|
||||||
|
itemsRead = len(dirents)
|
||||||
|
return dirents, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fusefs.NodeCreater = (*Dir)(nil)
|
||||||
|
|
||||||
|
// Create makes a new file
|
||||||
|
func (d *Dir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (node fusefs.Node, handle fusefs.Handle, err error) {
|
||||||
|
defer log.Trace(d, "name=%q", req.Name)("node=%v, handle=%v, err=%v", &node, &handle, &err)
|
||||||
|
file, err := d.Dir.Create(req.Name, int(req.Flags))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, (err)
|
||||||
|
}
|
||||||
|
fh, err := file.Open(int(req.Flags) | os.O_CREATE)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, (err)
|
||||||
|
}
|
||||||
|
node = &File{file, d.fsys}
|
||||||
|
file.SetSys(node) // cache the FUSE node for later
|
||||||
|
return node, &FileHandle{fh}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fusefs.NodeMkdirer = (*Dir)(nil)
|
||||||
|
|
||||||
|
// Mkdir creates a new directory
|
||||||
|
func (d *Dir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (node fusefs.Node, err error) {
|
||||||
|
defer log.Trace(d, "name=%q", req.Name)("node=%+v, err=%v", &node, &err)
|
||||||
|
dir, err := d.Dir.Mkdir(req.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, (err)
|
||||||
|
}
|
||||||
|
node = &Dir{dir, d.fsys}
|
||||||
|
dir.SetSys(node) // cache the FUSE node for later
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fusefs.NodeRemover = (*Dir)(nil)
|
||||||
|
|
||||||
|
// Remove removes the entry with the given name from
|
||||||
|
// the receiver, which must be a directory. The entry to be removed
|
||||||
|
// may correspond to a file (unlink) or to a directory (rmdir).
|
||||||
|
func (d *Dir) Remove(ctx context.Context, req *fuse.RemoveRequest) (err error) {
|
||||||
|
defer log.Trace(d, "name=%q", req.Name)("err=%v", &err)
|
||||||
|
err = d.Dir.RemoveName(req.Name)
|
||||||
|
if err != nil {
|
||||||
|
return (err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate a leaf in a directory
|
||||||
|
func (d *Dir) invalidateEntry(dirNode fusefs.Node, leaf string) {
|
||||||
|
fs.Debugf(dirNode, "Invalidating %q", leaf)
|
||||||
|
err := d.fsys.server.InvalidateEntry(dirNode, leaf)
|
||||||
|
if err != nil {
|
||||||
|
fs.Debugf(dirNode, "Failed to invalidate %q: %v", leaf, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check interface satisfied
|
||||||
|
var _ fusefs.NodeRenamer = (*Dir)(nil)
|
||||||
|
|
||||||
|
// Rename the file
|
||||||
|
func (d *Dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fusefs.Node) (err error) {
|
||||||
|
defer log.Trace(d, "oldName=%q, newName=%q, newDir=%+v", req.OldName, req.NewName, newDir)("err=%v", &err)
|
||||||
|
destDir, ok := newDir.(*Dir)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unknown Dir type %T", newDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.Dir.Rename(req.OldName, req.NewName, destDir.Dir)
|
||||||
|
if err != nil {
|
||||||
|
return (err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate the new directory entry so it gets re-read (in
|
||||||
|
// the background otherwise we cause a deadlock)
|
||||||
|
//
|
||||||
|
// See https://github.com/rclone/rclone/issues/4977 for why
|
||||||
|
go d.invalidateEntry(newDir, req.NewName)
|
||||||
|
//go d.invalidateEntry(d, req.OldName)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check interface satisfied
|
||||||
|
var _ fusefs.NodeFsyncer = (*Dir)(nil)
|
||||||
|
|
||||||
|
// Fsync the directory
|
||||||
|
func (d *Dir) Fsync(ctx context.Context, req *fuse.FsyncRequest) (err error) {
|
||||||
|
defer log.Trace(d, "")("err=%v", &err)
|
||||||
|
err = d.Dir.Sync()
|
||||||
|
if err != nil {
|
||||||
|
return (err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check interface satisfied
|
||||||
|
var _ fusefs.NodeLinker = (*Dir)(nil)
|
||||||
|
|
||||||
|
// Link creates a new directory entry in the receiver based on an
|
||||||
|
// existing Node. Receiver must be a directory.
|
||||||
|
func (d *Dir) Link(ctx context.Context, req *fuse.LinkRequest, old fusefs.Node) (newNode fusefs.Node, err error) {
|
||||||
|
defer log.Trace(d, "req=%v, old=%v", req, old)("new=%v, err=%v", &newNode, &err)
|
||||||
|
return nil, syscall.ENOSYS
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check interface satisfied
|
||||||
|
var _ fusefs.NodeMknoder = (*Dir)(nil)
|
||||||
|
|
||||||
|
// Mknod is called to create a file. Since we define create this will
|
||||||
|
// be called in preference, however NFS likes to call it for some
|
||||||
|
// reason. We don't actually create a file here just the Node.
|
||||||
|
func (d *Dir) Mknod(ctx context.Context, req *fuse.MknodRequest) (node fusefs.Node, err error) {
|
||||||
|
defer log.Trace(d, "name=%v, mode=%d, rdev=%d", req.Name, req.Mode, req.Rdev)("node=%v, err=%v", &node, &err)
|
||||||
|
if req.Rdev != 0 {
|
||||||
|
fs.Errorf(d, "Can't create device node %q", req.Name)
|
||||||
|
return nil, fuse.EIO
|
||||||
|
}
|
||||||
|
var cReq = fuse.CreateRequest{
|
||||||
|
Name: req.Name,
|
||||||
|
Flags: fuse.OpenFlags(os.O_CREATE | os.O_WRONLY),
|
||||||
|
Mode: req.Mode,
|
||||||
|
Umask: req.Umask,
|
||||||
|
}
|
||||||
|
var cResp fuse.CreateResponse
|
||||||
|
node, handle, err := d.Create(ctx, &cReq, &cResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = handle.(io.Closer).Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
125
pkg/mount/file.go
Normal file
125
pkg/mount/file.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
package mount
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"bazil.org/fuse"
|
||||||
|
fusefs "bazil.org/fuse/fs"
|
||||||
|
"github.com/rclone/rclone/fs/log"
|
||||||
|
"github.com/rclone/rclone/vfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File represents a file
|
||||||
|
type File struct {
|
||||||
|
*vfs.File
|
||||||
|
fsys *FS
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check interface satisfied
|
||||||
|
var _ fusefs.Node = (*File)(nil)
|
||||||
|
|
||||||
|
// Attr fills out the attributes for the file
|
||||||
|
func (f *File) Attr(ctx context.Context, a *fuse.Attr) (err error) {
|
||||||
|
defer log.Trace(f, "")("a=%+v, err=%v", a, &err)
|
||||||
|
a.Valid = f.fsys.opt.AttrTimeout
|
||||||
|
modTime := f.File.ModTime()
|
||||||
|
Size := uint64(f.File.Size())
|
||||||
|
Blocks := (Size + 511) / 512
|
||||||
|
a.Gid = f.VFS().Opt.GID
|
||||||
|
a.Uid = f.VFS().Opt.UID
|
||||||
|
a.Mode = f.VFS().Opt.FilePerms
|
||||||
|
a.Size = Size
|
||||||
|
a.Atime = modTime
|
||||||
|
a.Mtime = modTime
|
||||||
|
a.Ctime = modTime
|
||||||
|
a.Blocks = Blocks
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check interface satisfied
|
||||||
|
var _ fusefs.NodeSetattrer = (*File)(nil)
|
||||||
|
|
||||||
|
// Setattr handles attribute changes from FUSE. Currently supports ModTime and Size only
|
||||||
|
func (f *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) (err error) {
|
||||||
|
defer log.Trace(f, "a=%+v", req)("err=%v", &err)
|
||||||
|
if !f.VFS().Opt.NoModTime {
|
||||||
|
if req.Valid.Mtime() {
|
||||||
|
err = f.File.SetModTime(req.Mtime)
|
||||||
|
} else if req.Valid.MtimeNow() {
|
||||||
|
err = f.File.SetModTime(time.Now())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.Valid.Size() {
|
||||||
|
err = f.File.Truncate(int64(req.Size))
|
||||||
|
}
|
||||||
|
return (err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check interface satisfied
|
||||||
|
var _ fusefs.NodeOpener = (*File)(nil)
|
||||||
|
|
||||||
|
// Open the file for read or write
|
||||||
|
func (f *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fh fusefs.Handle, err error) {
|
||||||
|
defer log.Trace(f, "flags=%v", req.Flags)("fh=%v, err=%v", &fh, &err)
|
||||||
|
|
||||||
|
// fuse flags are based off syscall flags as are os flags, so
|
||||||
|
// should be compatible
|
||||||
|
handle, err := f.File.Open(int(req.Flags))
|
||||||
|
if err != nil {
|
||||||
|
return nil, (err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If size unknown then use direct io to read
|
||||||
|
if entry := handle.Node().DirEntry(); entry != nil && entry.Size() < 0 {
|
||||||
|
resp.Flags |= fuse.OpenDirectIO
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FileHandle{handle}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check interface satisfied
|
||||||
|
var _ fusefs.NodeFsyncer = (*File)(nil)
|
||||||
|
|
||||||
|
// Fsync the file
|
||||||
|
//
|
||||||
|
// Note that we don't do anything except return OK
|
||||||
|
func (f *File) Fsync(ctx context.Context, req *fuse.FsyncRequest) (err error) {
|
||||||
|
defer log.Trace(f, "")("err=%v", &err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getxattr gets an extended attribute by the given name from the
|
||||||
|
// node.
|
||||||
|
//
|
||||||
|
// If there is no xattr by that name, returns fuse.ErrNoXattr.
|
||||||
|
func (f *File) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
|
||||||
|
return syscall.ENOSYS // we never implement this
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fusefs.NodeGetxattrer = (*File)(nil)
|
||||||
|
|
||||||
|
// Listxattr lists the extended attributes recorded for the node.
|
||||||
|
func (f *File) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {
|
||||||
|
return syscall.ENOSYS // we never implement this
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fusefs.NodeListxattrer = (*File)(nil)
|
||||||
|
|
||||||
|
// Setxattr sets an extended attribute with the given name and
|
||||||
|
// value for the node.
|
||||||
|
func (f *File) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error {
|
||||||
|
return syscall.ENOSYS // we never implement this
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fusefs.NodeSetxattrer = (*File)(nil)
|
||||||
|
|
||||||
|
// Removexattr removes an extended attribute for the name.
|
||||||
|
//
|
||||||
|
// If there is no xattr by that name, returns fuse.ErrNoXattr.
|
||||||
|
func (f *File) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error {
|
||||||
|
return syscall.ENOSYS // we never implement this
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fusefs.NodeRemovexattrer = (*File)(nil)
|
||||||
82
pkg/mount/handle.go
Normal file
82
pkg/mount/handle.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package mount
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"bazil.org/fuse"
|
||||||
|
fusefs "bazil.org/fuse/fs"
|
||||||
|
"github.com/rclone/rclone/fs/log"
|
||||||
|
"github.com/rclone/rclone/vfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileHandle is an open for read file handle on a File
|
||||||
|
type FileHandle struct {
|
||||||
|
vfs.Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check interface satisfied
|
||||||
|
var _ fusefs.HandleReader = (*FileHandle)(nil)
|
||||||
|
|
||||||
|
// Read from the file handle
|
||||||
|
func (fh *FileHandle) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) (err error) {
|
||||||
|
var n int
|
||||||
|
defer log.Trace(fh, "len=%d, offset=%d", req.Size, req.Offset)("read=%d, err=%v", &n, &err)
|
||||||
|
data := make([]byte, req.Size)
|
||||||
|
n, err = fh.Handle.ReadAt(data, req.Offset)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
} else if err != nil {
|
||||||
|
return (err)
|
||||||
|
}
|
||||||
|
resp.Data = data[:n]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check interface satisfied
|
||||||
|
var _ fusefs.HandleWriter = (*FileHandle)(nil)
|
||||||
|
|
||||||
|
// Write data to the file handle
|
||||||
|
func (fh *FileHandle) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) (err error) {
|
||||||
|
defer log.Trace(fh, "len=%d, offset=%d", len(req.Data), req.Offset)("written=%d, err=%v", &resp.Size, &err)
|
||||||
|
n, err := fh.Handle.WriteAt(req.Data, req.Offset)
|
||||||
|
if err != nil {
|
||||||
|
return (err)
|
||||||
|
}
|
||||||
|
resp.Size = n
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check interface satisfied
|
||||||
|
var _ fusefs.HandleFlusher = (*FileHandle)(nil)
|
||||||
|
|
||||||
|
// Flush is called on each close() of a file descriptor. So if a
|
||||||
|
// filesystem wants to return write errors in close() and the file has
|
||||||
|
// cached dirty data, this is a good place to write back data and
|
||||||
|
// return any errors. Since many applications ignore close() errors
|
||||||
|
// this is not always useful.
|
||||||
|
//
|
||||||
|
// NOTE: The flush() method may be called more than once for each
|
||||||
|
// open(). This happens if more than one file descriptor refers to an
|
||||||
|
// opened file due to dup(), dup2() or fork() calls. It is not
|
||||||
|
// possible to determine if a flush is final, so each flush should be
|
||||||
|
// treated equally. Multiple write-flush sequences are relatively
|
||||||
|
// rare, so this shouldn't be a problem.
|
||||||
|
//
|
||||||
|
// Filesystems shouldn't assume that flush will always be called after
|
||||||
|
// some writes, or that if will be called at all.
|
||||||
|
func (fh *FileHandle) Flush(ctx context.Context, req *fuse.FlushRequest) (err error) {
|
||||||
|
defer log.Trace(fh, "")("err=%v", &err)
|
||||||
|
return (fh.Handle.Flush())
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fusefs.HandleReleaser = (*FileHandle)(nil)
|
||||||
|
|
||||||
|
// Release is called when we are finished with the file handle
|
||||||
|
//
|
||||||
|
// It isn't called directly from userspace so the error is ignored by
|
||||||
|
// the kernel
|
||||||
|
func (fh *FileHandle) Release(ctx context.Context, req *fuse.ReleaseRequest) (err error) {
|
||||||
|
defer log.Trace(fh, "")("err=%v", &err)
|
||||||
|
return (fh.Handle.Release())
|
||||||
|
}
|
||||||
113
pkg/mount/mount.go
Normal file
113
pkg/mount/mount.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package mount
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"bazil.org/fuse"
|
||||||
|
fusefs "bazil.org/fuse/fs"
|
||||||
|
"github.com/rclone/rclone/cmd/mountlib"
|
||||||
|
"github.com/rclone/rclone/fs"
|
||||||
|
"github.com/rclone/rclone/fs/log"
|
||||||
|
"github.com/rclone/rclone/vfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MountFn(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (<-chan error, func() error, error) {
|
||||||
|
|
||||||
|
f := VFS.Fs()
|
||||||
|
fs.Debugf(f, "Mounting on %q", mountpoint)
|
||||||
|
c, err := fuse.Mount(mountpoint, mountOptions(VFS, opt.DeviceName, opt)...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
filesys := NewFS(VFS, opt)
|
||||||
|
filesys.server = fusefs.New(c, nil)
|
||||||
|
|
||||||
|
// Serve the mount point in the background returning error to errChan
|
||||||
|
errChan := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
err := filesys.server.Serve(filesys)
|
||||||
|
closeErr := c.Close()
|
||||||
|
if err == nil {
|
||||||
|
err = closeErr
|
||||||
|
}
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
unmount := func() error {
|
||||||
|
// Shutdown the VFS
|
||||||
|
filesys.VFS.Shutdown()
|
||||||
|
return fuse.Unmount(mountpoint)
|
||||||
|
}
|
||||||
|
return errChan, unmount, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
func NewFS(VFS *vfs.VFS, opt *mountlib.Options) *FS {
|
||||||
|
fsys := &FS{
|
||||||
|
VFS: VFS,
|
||||||
|
f: VFS.Fs(),
|
||||||
|
opt: opt,
|
||||||
|
}
|
||||||
|
return fsys
|
||||||
|
}
|
||||||
|
|
||||||
|
type FS struct {
|
||||||
|
*vfs.VFS
|
||||||
|
f fs.Fs
|
||||||
|
opt *mountlib.Options
|
||||||
|
server *fusefs.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
// mountOptions configures the options from the command line flags
|
||||||
|
func mountOptions(VFS *vfs.VFS, device string, opt *mountlib.Options) (options []fuse.MountOption) {
|
||||||
|
options = []fuse.MountOption{
|
||||||
|
fuse.MaxReadahead(uint32(opt.MaxReadAhead)),
|
||||||
|
fuse.Subtype("rclone"),
|
||||||
|
fuse.FSName(device),
|
||||||
|
|
||||||
|
// Options from benchmarking in the fuse module
|
||||||
|
//fuse.MaxReadahead(64 * 1024 * 1024),
|
||||||
|
//fuse.WritebackCache(),
|
||||||
|
}
|
||||||
|
if opt.AsyncRead {
|
||||||
|
options = append(options, fuse.AsyncRead())
|
||||||
|
}
|
||||||
|
if opt.AllowNonEmpty {
|
||||||
|
options = append(options, fuse.AllowNonEmptyMount())
|
||||||
|
}
|
||||||
|
if opt.AllowOther {
|
||||||
|
options = append(options, fuse.AllowOther())
|
||||||
|
}
|
||||||
|
if opt.AllowRoot {
|
||||||
|
// options = append(options, fuse.AllowRoot())
|
||||||
|
fs.Errorf(nil, "Ignoring --allow-root. Support has been removed upstream - see https://github.com/bazil/fuse/issues/144 for more info")
|
||||||
|
}
|
||||||
|
if opt.DefaultPermissions {
|
||||||
|
options = append(options, fuse.DefaultPermissions())
|
||||||
|
}
|
||||||
|
if VFS.Opt.ReadOnly {
|
||||||
|
options = append(options, fuse.ReadOnly())
|
||||||
|
}
|
||||||
|
if opt.WritebackCache {
|
||||||
|
options = append(options, fuse.WritebackCache())
|
||||||
|
}
|
||||||
|
if opt.DaemonTimeout != 0 {
|
||||||
|
options = append(options, fuse.DaemonTimeout(fmt.Sprint(int(opt.DaemonTimeout.Seconds()))))
|
||||||
|
}
|
||||||
|
if len(opt.ExtraOptions) > 0 {
|
||||||
|
fs.Errorf(nil, "-o/--option not supported with this FUSE backend")
|
||||||
|
}
|
||||||
|
if len(opt.ExtraFlags) > 0 {
|
||||||
|
fs.Errorf(nil, "--fuse-flag not supported with this FUSE backend")
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root returns the root node
|
||||||
|
func (f *FS) Root() (node fusefs.Node, err error) {
|
||||||
|
defer log.Trace("", "")("node=%+v, err=%v", &node, &err)
|
||||||
|
root, err := f.VFS.Root()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Dir{root, f}, nil
|
||||||
|
}
|
||||||
@ -1,24 +1,17 @@
|
|||||||
package httper
|
package httper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type MountList struct {
|
type MountList struct {
|
||||||
MountPoints []struct {
|
MountPoints []MountPoints `json:"mountPoints"`
|
||||||
MountPoint string `json:"MountPoint"`
|
}
|
||||||
Fs string `json:"Fs"`
|
type MountPoints struct {
|
||||||
Icon string `json:"Icon"`
|
MountPoint string `json:"MountPoint"`
|
||||||
Name string `json:"Name"`
|
Fs string `json:"Fs"`
|
||||||
} `json:"mountPoints"`
|
Icon string `json:"Icon"`
|
||||||
|
Name string `json:"Name"`
|
||||||
}
|
}
|
||||||
type MountPoint struct {
|
type MountPoint struct {
|
||||||
MountPoint string `json:"mount_point"`
|
MountPoint string `json:"mount_point"`
|
||||||
@ -43,125 +36,126 @@ type RemotesResult struct {
|
|||||||
var UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
var UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
||||||
var DefaultTimeout = time.Second * 30
|
var DefaultTimeout = time.Second * 30
|
||||||
|
|
||||||
func NewRestyClient() *resty.Client {
|
// func NewRestyClient() *resty.Client {
|
||||||
|
|
||||||
unixSocket := "/var/run/rclone/rclone.sock"
|
// unixSocket := "/var/run/rclone/rclone.sock"
|
||||||
|
|
||||||
transport := http.Transport{
|
// transport := http.Transport{
|
||||||
Dial: func(_, _ string) (net.Conn, error) {
|
// Dial: func(_, _ string) (net.Conn, error) {
|
||||||
return net.Dial("unix", unixSocket)
|
// return net.Dial("unix", unixSocket)
|
||||||
},
|
// },
|
||||||
}
|
// }
|
||||||
|
|
||||||
client := resty.New()
|
// client := resty.New()
|
||||||
|
|
||||||
client.SetTransport(&transport).SetBaseURL("http://localhost")
|
// client.SetTransport(&transport).SetBaseURL("http://localhost")
|
||||||
client.SetRetryCount(3).SetRetryWaitTime(5*time.Second).SetTimeout(DefaultTimeout).SetHeader("User-Agent", UserAgent)
|
// client.SetRetryCount(3).SetRetryWaitTime(5*time.Second).SetTimeout(DefaultTimeout).SetHeader("User-Agent", UserAgent)
|
||||||
return client
|
// return client
|
||||||
}
|
// }
|
||||||
|
|
||||||
func GetMountList() (MountList, error) {
|
// func GetMountList() (MountList, error) {
|
||||||
var result MountList
|
// var result MountList
|
||||||
res, err := NewRestyClient().R().Post("/mount/listmounts")
|
// res, err := NewRestyClient().R().Post("/mount/listmounts")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return result, err
|
// return result, err
|
||||||
}
|
// }
|
||||||
if res.StatusCode() != 200 {
|
// if res.StatusCode() != 200 {
|
||||||
return result, fmt.Errorf("get mount list failed")
|
// return result, fmt.Errorf("get mount list failed")
|
||||||
}
|
// }
|
||||||
json.Unmarshal(res.Body(), &result)
|
// json.Unmarshal(res.Body(), &result)
|
||||||
for i := 0; i < len(result.MountPoints); i++ {
|
// for i := 0; i < len(result.MountPoints); i++ {
|
||||||
result.MountPoints[i].Fs = result.MountPoints[i].Fs[:len(result.MountPoints[i].Fs)-1]
|
// result.MountPoints[i].Fs = result.MountPoints[i].Fs[:len(result.MountPoints[i].Fs)-1]
|
||||||
}
|
// }
|
||||||
return result, err
|
// return result, err
|
||||||
}
|
// }
|
||||||
func Mount(mountPoint string, fs string) error {
|
//
|
||||||
res, err := NewRestyClient().R().SetFormData(map[string]string{
|
// func Mount(mountPoint string, fs string) error {
|
||||||
"mountPoint": mountPoint,
|
// res, err := NewRestyClient().R().SetFormData(map[string]string{
|
||||||
"fs": fs,
|
// "mountPoint": mountPoint,
|
||||||
"mountOpt": `{"AllowOther": true}`,
|
// "fs": fs,
|
||||||
}).Post("/mount/mount")
|
// "mountOpt": `{"AllowOther": true}`,
|
||||||
if err != nil {
|
// }).Post("/mount/mount")
|
||||||
return err
|
// if err != nil {
|
||||||
}
|
// return err
|
||||||
if res.StatusCode() != 200 {
|
// }
|
||||||
return fmt.Errorf("mount failed")
|
// if res.StatusCode() != 200 {
|
||||||
}
|
// return fmt.Errorf("mount failed")
|
||||||
logger.Info("mount then", zap.Any("res", res.Body()))
|
// }
|
||||||
return nil
|
// logger.Info("mount then", zap.Any("res", res.Body()))
|
||||||
}
|
// return nil
|
||||||
func Unmount(mountPoint string) error {
|
// }
|
||||||
res, err := NewRestyClient().R().SetFormData(map[string]string{
|
// func Unmount(mountPoint string) error {
|
||||||
"mountPoint": mountPoint,
|
// res, err := NewRestyClient().R().SetFormData(map[string]string{
|
||||||
}).Post("/mount/unmount")
|
// "mountPoint": mountPoint,
|
||||||
if err != nil {
|
// }).Post("/mount/unmount")
|
||||||
logger.Error("when unmount", zap.Error(err))
|
// if err != nil {
|
||||||
return err
|
// logger.Error("when unmount", zap.Error(err))
|
||||||
}
|
// return err
|
||||||
if res.StatusCode() != 200 {
|
// }
|
||||||
logger.Error("then unmount failed", zap.Any("res", res.Body()))
|
// if res.StatusCode() != 200 {
|
||||||
return fmt.Errorf("unmount failed")
|
// logger.Error("then unmount failed", zap.Any("res", res.Body()))
|
||||||
}
|
// return fmt.Errorf("unmount failed")
|
||||||
logger.Info("unmount then", zap.Any("res", res.Body()))
|
// }
|
||||||
return nil
|
// logger.Info("unmount then", zap.Any("res", res.Body()))
|
||||||
}
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
func CreateConfig(data map[string]string, name, t string) error {
|
// func CreateConfig(data map[string]string, name, t string) error {
|
||||||
data["config_is_local"] = "false"
|
// data["config_is_local"] = "false"
|
||||||
dataStr, _ := json.Marshal(data)
|
// dataStr, _ := json.Marshal(data)
|
||||||
res, err := NewRestyClient().R().SetFormData(map[string]string{
|
// res, err := NewRestyClient().R().SetFormData(map[string]string{
|
||||||
"name": name,
|
// "name": name,
|
||||||
"parameters": string(dataStr),
|
// "parameters": string(dataStr),
|
||||||
"type": t,
|
// "type": t,
|
||||||
}).Post("/config/create")
|
// }).Post("/config/create")
|
||||||
logger.Info("when create config then", zap.Any("res", res.Body()))
|
// logger.Info("when create config then", zap.Any("res", res.Body()))
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
if res.StatusCode() != 200 {
|
// if res.StatusCode() != 200 {
|
||||||
return fmt.Errorf("create config failed")
|
// return fmt.Errorf("create config failed")
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func GetConfigByName(name string) (map[string]string, error) {
|
// func GetConfigByName(name string) (map[string]string, error) {
|
||||||
|
|
||||||
res, err := NewRestyClient().R().SetFormData(map[string]string{
|
// res, err := NewRestyClient().R().SetFormData(map[string]string{
|
||||||
"name": name,
|
// "name": name,
|
||||||
}).Post("/config/get")
|
// }).Post("/config/get")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
if res.StatusCode() != 200 {
|
// if res.StatusCode() != 200 {
|
||||||
return nil, fmt.Errorf("create config failed")
|
// return nil, fmt.Errorf("create config failed")
|
||||||
}
|
// }
|
||||||
var result map[string]string
|
// var result map[string]string
|
||||||
json.Unmarshal(res.Body(), &result)
|
// json.Unmarshal(res.Body(), &result)
|
||||||
return result, nil
|
// return result, nil
|
||||||
}
|
// }
|
||||||
func GetAllConfigName() (RemotesResult, error) {
|
// func GetAllConfigName() (RemotesResult, error) {
|
||||||
var result RemotesResult
|
// var result RemotesResult
|
||||||
res, err := NewRestyClient().R().SetFormData(map[string]string{}).Post("/config/listremotes")
|
// res, err := NewRestyClient().R().SetFormData(map[string]string{}).Post("/config/listremotes")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return result, err
|
// return result, err
|
||||||
}
|
// }
|
||||||
if res.StatusCode() != 200 {
|
// if res.StatusCode() != 200 {
|
||||||
return result, fmt.Errorf("get config failed")
|
// return result, fmt.Errorf("get config failed")
|
||||||
}
|
// }
|
||||||
|
|
||||||
json.Unmarshal(res.Body(), &result)
|
// json.Unmarshal(res.Body(), &result)
|
||||||
return result, nil
|
// return result, nil
|
||||||
}
|
// }
|
||||||
func DeleteConfigByName(name string) error {
|
// func DeleteConfigByName(name string) error {
|
||||||
res, err := NewRestyClient().R().SetFormData(map[string]string{
|
// res, err := NewRestyClient().R().SetFormData(map[string]string{
|
||||||
"name": name,
|
// "name": name,
|
||||||
}).Post("/config/delete")
|
// }).Post("/config/delete")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
if res.StatusCode() != 200 {
|
// if res.StatusCode() != 200 {
|
||||||
return fmt.Errorf("delete config failed")
|
// return fmt.Errorf("delete config failed")
|
||||||
}
|
// }
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|||||||
@ -93,9 +93,4 @@ func InitNetworkMount() {
|
|||||||
connection.Directories = strings.Join(directories, ",")
|
connection.Directories = strings.Join(directories, ",")
|
||||||
service.MyService.Connections().UpdateConnection(&connection)
|
service.MyService.Connections().UpdateConnection(&connection)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := service.MyService.Storage().CheckAndMountAll()
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("mount storage err", zap.Any("err", err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
14
route/v1.go
14
route/v1.go
@ -36,7 +36,7 @@ func InitV1Router() *gin.Engine {
|
|||||||
r.GET("/ping", func(ctx *gin.Context) {
|
r.GET("/ping", func(ctx *gin.Context) {
|
||||||
ctx.String(200, "pong")
|
ctx.String(200, "pong")
|
||||||
})
|
})
|
||||||
r.GET("/v1/recover/:type", v1.GetRecoverStorage)
|
|
||||||
v1Group := r.Group("/v1")
|
v1Group := r.Group("/v1")
|
||||||
|
|
||||||
v1Group.Use(jwt.ExceptLocalhost())
|
v1Group.Use(jwt.ExceptLocalhost())
|
||||||
@ -98,17 +98,7 @@ func InitV1Router() *gin.Engine {
|
|||||||
v1FileGroup.GET("/ws", v1.ConnectWebSocket)
|
v1FileGroup.GET("/ws", v1.ConnectWebSocket)
|
||||||
v1FileGroup.GET("/peers", v1.GetPeers)
|
v1FileGroup.GET("/peers", v1.GetPeers)
|
||||||
}
|
}
|
||||||
v1CloudGroup := v1Group.Group("/cloud")
|
|
||||||
v1CloudGroup.Use()
|
|
||||||
{
|
|
||||||
v1CloudGroup.GET("", v1.ListStorages)
|
|
||||||
v1CloudGroup.DELETE("", v1.DeleteStorage)
|
|
||||||
}
|
|
||||||
v1DriverGroup := v1Group.Group("/driver")
|
|
||||||
v1DriverGroup.Use()
|
|
||||||
{
|
|
||||||
v1DriverGroup.GET("", v1.ListDriverInfo)
|
|
||||||
}
|
|
||||||
v1FolderGroup := v1Group.Group("/folder")
|
v1FolderGroup := v1Group.Group("/folder")
|
||||||
v1FolderGroup.Use()
|
v1FolderGroup.Use()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
package v1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/op"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/model"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ListDriverInfo(c *gin.Context) {
|
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: op.GetDriverInfoMap()})
|
|
||||||
}
|
|
||||||
467
route/v1/file.go
467
route/v1/file.go
@ -1,8 +1,6 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -19,20 +17,16 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
|
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/conf"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/driver"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/model"
|
"github.com/IceWhaleTech/CasaOS/model"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/utils"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
|
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
|
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
|
||||||
"github.com/IceWhaleTech/CasaOS/service"
|
"github.com/IceWhaleTech/CasaOS/service"
|
||||||
model2 "github.com/IceWhaleTech/CasaOS/service/model"
|
model2 "github.com/IceWhaleTech/CasaOS/service/model"
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/sign"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
uuid "github.com/satori/go.uuid"
|
uuid "github.com/satori/go.uuid"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@ -40,17 +34,32 @@ import (
|
|||||||
"github.com/h2non/filetype"
|
"github.com/h2non/filetype"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type ListReq struct {
|
||||||
// 升级成 WebSocket 协议
|
model.PageReq
|
||||||
upgraderFile = websocket.Upgrader{
|
Path string `json:"path" form:"path"`
|
||||||
// 允许CORS跨域请求
|
//Refresh bool `json:"refresh"`
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
}
|
||||||
return true
|
type ObjResp struct {
|
||||||
},
|
Name string `json:"name"`
|
||||||
}
|
Size int64 `json:"size"`
|
||||||
conn *websocket.Conn
|
IsDir bool `json:"is_dir"`
|
||||||
err error
|
Modified time.Time `json:"modified"`
|
||||||
)
|
Sign string `json:"sign"`
|
||||||
|
Thumb string `json:"thumb"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Date time.Time `json:"date"`
|
||||||
|
Extensions map[string]interface{} `json:"extensions"`
|
||||||
|
}
|
||||||
|
type FsListResp struct {
|
||||||
|
Content []ObjResp `json:"content"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Readme string `json:"readme,omitempty"`
|
||||||
|
Write bool `json:"write,omitempty"`
|
||||||
|
Provider string `json:"provider,omitempty"`
|
||||||
|
Index int `json:"index"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
}
|
||||||
|
|
||||||
// @Summary 读取文件
|
// @Summary 读取文件
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
@ -784,427 +793,3 @@ func GetSize(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: size})
|
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: size})
|
||||||
}
|
}
|
||||||
func Proxy(c *gin.Context) {
|
|
||||||
rawPath := c.Query("path")
|
|
||||||
filename := filepath.Base(rawPath)
|
|
||||||
storage, err := service.MyService.FsService().GetStorage(rawPath)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(500, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if canProxy(storage, filename) {
|
|
||||||
downProxyUrl := storage.GetStorage().DownProxyUrl
|
|
||||||
if downProxyUrl != "" {
|
|
||||||
_, ok := c.GetQuery("d")
|
|
||||||
if !ok {
|
|
||||||
URL := fmt.Sprintf("%s%s?sign=%s",
|
|
||||||
strings.Split(downProxyUrl, "\n")[0],
|
|
||||||
utils.EncodePath(rawPath, true),
|
|
||||||
sign.Sign(rawPath))
|
|
||||||
c.Redirect(302, URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
link, file, err := service.MyService.FsService().Link(c, rawPath, model.LinkArgs{
|
|
||||||
Header: c.Request.Header,
|
|
||||||
Type: c.Query("type"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = CommonProxy(c.Writer, c.Request, link, file)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: "proxy not allowed"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO need optimize
|
|
||||||
// when should be proxy?
|
|
||||||
// 1. config.MustProxy()
|
|
||||||
// 2. storage.WebProxy
|
|
||||||
// 3. proxy_types
|
|
||||||
func shouldProxy(storage driver.Driver, filename string) bool {
|
|
||||||
if storage.Config().MustProxy() || storage.GetStorage().WebProxy {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if utils.SliceContains(conf.SlicesMap[conf.ProxyTypes], utils.Ext(filename)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO need optimize
|
|
||||||
// when can be proxy?
|
|
||||||
// 1. text file
|
|
||||||
// 2. config.MustProxy()
|
|
||||||
// 3. storage.WebProxy
|
|
||||||
// 4. proxy_types
|
|
||||||
// solution: text_file + shouldProxy()
|
|
||||||
func canProxy(storage driver.Driver, filename string) bool {
|
|
||||||
if storage.Config().MustProxy() || storage.GetStorage().WebProxy || storage.GetStorage().WebdavProxy() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if utils.SliceContains(conf.SlicesMap[conf.ProxyTypes], utils.Ext(filename)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if utils.SliceContains(conf.SlicesMap[conf.TextTypes], utils.Ext(filename)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var HttpClient = &http.Client{}
|
|
||||||
|
|
||||||
func CommonProxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.Obj) error {
|
|
||||||
// read data with native
|
|
||||||
var err error
|
|
||||||
if link.Data != nil {
|
|
||||||
defer func() {
|
|
||||||
_ = link.Data.Close()
|
|
||||||
}()
|
|
||||||
w.Header().Set("Content-Type", "application/octet-stream")
|
|
||||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, file.GetName(), url.QueryEscape(file.GetName())))
|
|
||||||
w.Header().Set("Content-Length", strconv.FormatInt(file.GetSize(), 10))
|
|
||||||
if link.Header != nil {
|
|
||||||
// TODO clean header with blacklist or whitelist
|
|
||||||
link.Header.Del("set-cookie")
|
|
||||||
for h, val := range link.Header {
|
|
||||||
w.Header()[h] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if link.Status == 0 {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
} else {
|
|
||||||
w.WriteHeader(link.Status)
|
|
||||||
}
|
|
||||||
_, err = io.Copy(w, link.Data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// local file
|
|
||||||
if link.FilePath != nil && *link.FilePath != "" {
|
|
||||||
f, err := os.Open(*link.FilePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = f.Close()
|
|
||||||
}()
|
|
||||||
fileStat, err := os.Stat(*link.FilePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, file.GetName(), url.QueryEscape(file.GetName())))
|
|
||||||
http.ServeContent(w, r, file.GetName(), fileStat.ModTime(), f)
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
req, err := http.NewRequest(link.Method, link.URL, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for h, val := range r.Header {
|
|
||||||
if utils.SliceContains(conf.SlicesMap[conf.ProxyIgnoreHeaders], strings.ToLower(h)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
req.Header[h] = val
|
|
||||||
}
|
|
||||||
for h, val := range link.Header {
|
|
||||||
req.Header[h] = val
|
|
||||||
}
|
|
||||||
res, err := HttpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = res.Body.Close()
|
|
||||||
}()
|
|
||||||
logger.Info("proxy status", zap.Any("status", res.StatusCode))
|
|
||||||
// TODO clean header with blacklist or whitelist
|
|
||||||
res.Header.Del("set-cookie")
|
|
||||||
for h, v := range res.Header {
|
|
||||||
w.Header()[h] = v
|
|
||||||
}
|
|
||||||
w.WriteHeader(res.StatusCode)
|
|
||||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, file.GetName(), url.QueryEscape(file.GetName())))
|
|
||||||
if res.StatusCode >= 400 {
|
|
||||||
all, _ := ioutil.ReadAll(res.Body)
|
|
||||||
msg := string(all)
|
|
||||||
logger.Info("msg", zap.Any("msg", msg))
|
|
||||||
|
|
||||||
return errors.New(msg)
|
|
||||||
}
|
|
||||||
_, err = io.Copy(w, res.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type CenterHandler struct {
|
|
||||||
// 广播通道,有数据则循环每个用户广播出去
|
|
||||||
broadcast chan []byte
|
|
||||||
// 注册通道,有用户进来 则推到用户集合map中
|
|
||||||
register chan *Client
|
|
||||||
// 注销通道,有用户关闭连接 则将该用户剔出集合map中
|
|
||||||
unregister chan *Client
|
|
||||||
// 用户集合,每个用户本身也在跑两个协程,监听用户的读、写的状态
|
|
||||||
clients map[string]*Client
|
|
||||||
}
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
handler *CenterHandler
|
|
||||||
conn *websocket.Conn
|
|
||||||
// 每个用户自己的循环跑起来的状态监控
|
|
||||||
send chan []byte
|
|
||||||
ID string `json:"id"`
|
|
||||||
IP string `json:"ip"`
|
|
||||||
Name service.Name `json:"name"`
|
|
||||||
RtcSupported bool `json:"rtcSupported"`
|
|
||||||
TimerId int `json:"timerId"`
|
|
||||||
LastBeat time.Time `json:"lastBeat"`
|
|
||||||
Offline bool `json:"offline"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConnectWebSocket(c *gin.Context) {
|
|
||||||
peerId := c.Query("peer")
|
|
||||||
writer := c.Writer
|
|
||||||
request := c.Request
|
|
||||||
key := uuid.NewV4().String()
|
|
||||||
//peerModel := service.MyService.Peer().GetPeerByUserAgent(c.Request.UserAgent())
|
|
||||||
peerModel := model2.PeerDriveDBModel{}
|
|
||||||
name := service.GetName(request)
|
|
||||||
if conn, err = upgraderFile.Upgrade(writer, request, writer.Header()); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
client := &Client{handler: &handler, conn: conn, send: make(chan []byte, 256), ID: service.GetPeerId(request, key), IP: service.GetIP(request), Name: name, RtcSupported: true, TimerId: 0, LastBeat: time.Now()}
|
|
||||||
if peerId != "" || len(peerModel.ID) > 0 {
|
|
||||||
if len(peerModel.ID) == 0 {
|
|
||||||
peerModel = service.MyService.Peer().GetPeerByID(peerId)
|
|
||||||
}
|
|
||||||
if len(peerModel.ID) > 0 {
|
|
||||||
key = peerId
|
|
||||||
client.ID = peerModel.ID
|
|
||||||
client.Name = service.GetNameByDB(peerModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var list = service.MyService.Peer().GetPeers()
|
|
||||||
if len(peerModel.ID) == 0 {
|
|
||||||
peerModel.ID = key
|
|
||||||
peerModel.DisplayName = name.DisplayName
|
|
||||||
peerModel.DeviceName = name.DeviceName
|
|
||||||
peerModel.OS = name.OS
|
|
||||||
peerModel.Browser = name.Browser
|
|
||||||
peerModel.UserAgent = c.Request.UserAgent()
|
|
||||||
peerModel.IP = client.IP
|
|
||||||
service.MyService.Peer().CreatePeer(&peerModel)
|
|
||||||
list = append(list, peerModel)
|
|
||||||
}
|
|
||||||
|
|
||||||
cookie := http.Cookie{
|
|
||||||
Name: "peerid",
|
|
||||||
Value: key,
|
|
||||||
Path: "/",
|
|
||||||
}
|
|
||||||
http.SetCookie(writer, &cookie)
|
|
||||||
|
|
||||||
// 推给监控中心注册到用户集合中
|
|
||||||
handler.register <- client
|
|
||||||
if len(list) > 10 {
|
|
||||||
fmt.Println("有溢出", list)
|
|
||||||
}
|
|
||||||
if len(list) > 10 {
|
|
||||||
kickoutList := []Client{}
|
|
||||||
count := len(list) - 10
|
|
||||||
for i := len(list) - 1; count > 0 && i > -1; i-- {
|
|
||||||
if _, ok := handler.clients[list[i].ID]; !ok {
|
|
||||||
count--
|
|
||||||
kickoutList = append(kickoutList, Client{ID: list[i].ID, Name: service.GetNameByDB(list[i]), IP: list[i].IP, Offline: true})
|
|
||||||
service.MyService.Peer().DeletePeer(list[i].ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(kickoutList) > 0 {
|
|
||||||
other := make(map[string]interface{})
|
|
||||||
other["type"] = "kickout"
|
|
||||||
other["peers"] = kickoutList
|
|
||||||
otherBy, err := json.Marshal(other)
|
|
||||||
fmt.Println(err)
|
|
||||||
client.handler.broadcast <- otherBy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
list = service.MyService.Peer().GetPeers()
|
|
||||||
if len(list) > 10 {
|
|
||||||
fmt.Println("解决完后依然有溢出", list)
|
|
||||||
}
|
|
||||||
clients := []Client{}
|
|
||||||
for _, v := range client.handler.clients {
|
|
||||||
if _, ok := handler.clients[v.ID]; ok {
|
|
||||||
clients = append(clients, *handler.clients[v.ID])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
other := make(map[string]interface{})
|
|
||||||
other["type"] = "peers"
|
|
||||||
other["peers"] = clients
|
|
||||||
otherBy, err := json.Marshal(other)
|
|
||||||
fmt.Println(err)
|
|
||||||
client.handler.broadcast <- otherBy
|
|
||||||
|
|
||||||
pmsg := make(map[string]interface{})
|
|
||||||
pmsg["type"] = "peer-joined"
|
|
||||||
pmsg["peer"] = client
|
|
||||||
pby, err := json.Marshal(pmsg)
|
|
||||||
fmt.Println(err)
|
|
||||||
client.handler.broadcast <- pby
|
|
||||||
|
|
||||||
data := make(map[string]string)
|
|
||||||
data["displayName"] = client.Name.DisplayName
|
|
||||||
data["deviceName"] = client.Name.DeviceName
|
|
||||||
data["id"] = client.ID
|
|
||||||
msg := make(map[string]interface{})
|
|
||||||
msg["type"] = "display-name"
|
|
||||||
msg["message"] = data
|
|
||||||
by, _ := json.Marshal(msg)
|
|
||||||
client.send <- by
|
|
||||||
|
|
||||||
// 每个 client 都挂起 2 个新的协程,监控读、写状态
|
|
||||||
go client.writePump()
|
|
||||||
go client.readPump()
|
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
|
|
||||||
}
|
|
||||||
|
|
||||||
var handler = CenterHandler{broadcast: make(chan []byte),
|
|
||||||
register: make(chan *Client),
|
|
||||||
unregister: make(chan *Client),
|
|
||||||
clients: make(map[string]*Client)}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// 起个协程跑起来,监听注册、注销、消息 3 个 channel
|
|
||||||
go handler.monitoring()
|
|
||||||
|
|
||||||
crontab := cron.New(cron.WithSeconds()) //精确到秒
|
|
||||||
//定义定时器调用的任务函数
|
|
||||||
|
|
||||||
task := func() {
|
|
||||||
handler.broadcast <- []byte(`{"type":"ping"}`)
|
|
||||||
}
|
|
||||||
//定时任务
|
|
||||||
spec := "*/20 * * * * ?" //cron表达式,每五秒一次
|
|
||||||
// 添加定时任务,
|
|
||||||
crontab.AddFunc(spec, task)
|
|
||||||
// 启动定时器
|
|
||||||
crontab.Start()
|
|
||||||
}
|
|
||||||
func (c *Client) writePump() {
|
|
||||||
defer func() {
|
|
||||||
c.handler.unregister <- c
|
|
||||||
c.conn.Close()
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
// 广播推过来的新消息,马上通过websocket推给自己
|
|
||||||
message, _ := <-c.send
|
|
||||||
if err := c.conn.WriteMessage(websocket.TextMessage, message); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读,监听客户端是否有推送内容过来服务端
|
|
||||||
func (c *Client) readPump() {
|
|
||||||
defer func() {
|
|
||||||
c.handler.unregister <- c
|
|
||||||
c.conn.Close()
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
// 循环监听是否该用户是否要发言
|
|
||||||
_, message, err := c.conn.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
// 异常关闭的处理
|
|
||||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
|
||||||
log.Printf("error: %v", err)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// 要的话,推给广播中心,广播中心再推给每个用户
|
|
||||||
|
|
||||||
t := gjson.GetBytes(message, "type")
|
|
||||||
if t.String() == "disconnect" {
|
|
||||||
c.handler.unregister <- c
|
|
||||||
c.conn.Close()
|
|
||||||
// clients := []Client{}
|
|
||||||
// list := service.MyService.Peer().GetPeers()
|
|
||||||
// for _, v := range list {
|
|
||||||
// if _, ok := handler.clients[v.ID]; ok {
|
|
||||||
// clients = append(clients, *handler.clients[v.ID])
|
|
||||||
// } else {
|
|
||||||
// clients = append(clients, Client{ID: v.ID, Name: service.GetNameByDB(v), IP: v.IP, Offline: true})
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// other := make(map[string]interface{})
|
|
||||||
// other["type"] = "peers"
|
|
||||||
// other["peers"] = clients
|
|
||||||
// otherBy, err := json.Marshal(other)
|
|
||||||
// fmt.Println(err)
|
|
||||||
c.handler.broadcast <- []byte(`{"type":"peer-left","peerId":"` + c.ID + `"}`)
|
|
||||||
//c.handler.broadcast <- otherBy
|
|
||||||
break
|
|
||||||
} else if t.String() == "pong" {
|
|
||||||
c.LastBeat = time.Now()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
to := gjson.GetBytes(message, "to")
|
|
||||||
|
|
||||||
if len(to.String()) > 0 {
|
|
||||||
toC := c.handler.clients[to.String()]
|
|
||||||
if toC == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
data := map[string]interface{}{}
|
|
||||||
json.Unmarshal(message, &data)
|
|
||||||
data["sender"] = c.ID
|
|
||||||
delete(data, "to")
|
|
||||||
message, err = json.Marshal(data)
|
|
||||||
toC.send <- message
|
|
||||||
}
|
|
||||||
|
|
||||||
c.handler.broadcast <- message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (ch *CenterHandler) monitoring() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
// 注册,新用户连接过来会推进注册通道,这里接收推进来的用户指针
|
|
||||||
case client := <-ch.register:
|
|
||||||
ch.clients[client.ID] = client
|
|
||||||
// 注销,关闭连接或连接异常会将用户推出群聊
|
|
||||||
case client := <-ch.unregister:
|
|
||||||
delete(ch.clients, client.ID)
|
|
||||||
// 消息,监听到有新消息到来
|
|
||||||
case message := <-ch.broadcast:
|
|
||||||
println("消息来了,message:" + string(message))
|
|
||||||
// 推送给每个用户的通道,每个用户都有跑协程起了writePump的监听
|
|
||||||
for _, client := range ch.clients {
|
|
||||||
client.send <- message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func GetPeers(c *gin.Context) {
|
|
||||||
peers := service.MyService.Peer().GetPeers()
|
|
||||||
for i := 0; i < len(peers); i++ {
|
|
||||||
if _, ok := handler.clients[peers[i].ID]; ok {
|
|
||||||
peers[i].Online = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: peers})
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,97 +0,0 @@
|
|||||||
package v1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS/model"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/service"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ListReq struct {
|
|
||||||
model.PageReq
|
|
||||||
Path string `json:"path" form:"path"`
|
|
||||||
//Refresh bool `json:"refresh"`
|
|
||||||
}
|
|
||||||
type ObjResp struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
IsDir bool `json:"is_dir"`
|
|
||||||
Modified time.Time `json:"modified"`
|
|
||||||
Sign string `json:"sign"`
|
|
||||||
Thumb string `json:"thumb"`
|
|
||||||
Type int `json:"type"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
Date time.Time `json:"date"`
|
|
||||||
Extensions map[string]interface{} `json:"extensions"`
|
|
||||||
}
|
|
||||||
type FsListResp struct {
|
|
||||||
Content []ObjResp `json:"content"`
|
|
||||||
Total int64 `json:"total"`
|
|
||||||
Readme string `json:"readme,omitempty"`
|
|
||||||
Write bool `json:"write,omitempty"`
|
|
||||||
Provider string `json:"provider,omitempty"`
|
|
||||||
Index int `json:"index"`
|
|
||||||
Size int `json:"size"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func FsList(c *gin.Context) {
|
|
||||||
var req ListReq
|
|
||||||
if err := c.ShouldBind(&req); err != nil {
|
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
req.Validate()
|
|
||||||
objs, err := service.MyService.FsService().FList(c, req.Path, false)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
total, objs := pagination(objs, &req.PageReq)
|
|
||||||
provider := "unknown"
|
|
||||||
storage, err := service.MyService.FsService().GetStorage(req.Path)
|
|
||||||
if err == nil {
|
|
||||||
provider = storage.GetStorage().Driver
|
|
||||||
}
|
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: FsListResp{
|
|
||||||
Content: toObjsResp(objs, req.Path, false),
|
|
||||||
Total: int64(total),
|
|
||||||
Readme: "",
|
|
||||||
Write: false,
|
|
||||||
Provider: provider,
|
|
||||||
}})
|
|
||||||
}
|
|
||||||
func pagination(objs []model.Obj, req *model.PageReq) (int, []model.Obj) {
|
|
||||||
pageIndex, pageSize := req.Index, req.Size
|
|
||||||
total := len(objs)
|
|
||||||
start := (pageIndex - 1) * pageSize
|
|
||||||
if start > total {
|
|
||||||
return total, []model.Obj{}
|
|
||||||
}
|
|
||||||
end := start + pageSize
|
|
||||||
if end > total {
|
|
||||||
end = total
|
|
||||||
}
|
|
||||||
return total, objs[start:end]
|
|
||||||
}
|
|
||||||
|
|
||||||
func toObjsResp(objs []model.Obj, parent string, encrypt bool) []ObjResp {
|
|
||||||
var resp []ObjResp
|
|
||||||
for _, obj := range objs {
|
|
||||||
thumb, _ := model.GetThumb(obj)
|
|
||||||
resp = append(resp, ObjResp{
|
|
||||||
Name: obj.GetName(),
|
|
||||||
Size: obj.GetSize(),
|
|
||||||
IsDir: obj.IsDir(),
|
|
||||||
Modified: obj.ModTime(),
|
|
||||||
Sign: "",
|
|
||||||
Path: filepath.Join(parent, obj.GetName()),
|
|
||||||
Thumb: thumb,
|
|
||||||
Type: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
@ -1,203 +0,0 @@
|
|||||||
package v1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/drivers/dropbox"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/drivers/google_drive"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/service"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetRecoverStorage(c *gin.Context) {
|
|
||||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
|
||||||
t := c.Param("type")
|
|
||||||
currentTime := time.Now().UTC()
|
|
||||||
currentDate := time.Now().UTC().Format("2006-01-02")
|
|
||||||
notify := make(map[string]interface{})
|
|
||||||
if t == "GoogleDrive" {
|
|
||||||
add := google_drive.Addition{}
|
|
||||||
add.Code = c.Query("code")
|
|
||||||
if len(add.Code) == 0 {
|
|
||||||
c.String(200, `<p>Code cannot be empty</p><script>window.close()</script>`)
|
|
||||||
notify["status"] = "fail"
|
|
||||||
notify["message"] = "Code cannot be empty"
|
|
||||||
logger.Error("Then code is empty: ", zap.String("code", add.Code), zap.Any("name", "google_drive"))
|
|
||||||
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
add.RootFolderID = "root"
|
|
||||||
add.ClientID = google_drive.CLIENTID
|
|
||||||
add.ClientSecret = google_drive.CLIENTSECRET
|
|
||||||
|
|
||||||
var google_drive google_drive.GoogleDrive
|
|
||||||
google_drive.Addition = add
|
|
||||||
err := google_drive.Init(c)
|
|
||||||
if err != nil {
|
|
||||||
c.String(200, `<p>Initialization failure:`+err.Error()+`</p><script>window.close()</script>`)
|
|
||||||
notify["status"] = "fail"
|
|
||||||
notify["message"] = "Initialization failure"
|
|
||||||
logger.Error("Then init error: ", zap.Error(err), zap.Any("name", "google_drive"))
|
|
||||||
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
username, err := google_drive.GetUserInfo(c)
|
|
||||||
if err != nil {
|
|
||||||
c.String(200, `<p>Failed to get user information:`+err.Error()+`</p><script>window.close()</script>`)
|
|
||||||
notify["status"] = "fail"
|
|
||||||
notify["message"] = "Failed to get user information"
|
|
||||||
logger.Error("Then get user info error: ", zap.Error(err), zap.Any("name", "google_drive"))
|
|
||||||
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dmap := make(map[string]string)
|
|
||||||
dmap["username"] = username
|
|
||||||
configs, err := service.MyService.Storage().GetConfig()
|
|
||||||
if err != nil {
|
|
||||||
c.String(200, `<p>Failed to get rclone config:`+err.Error()+`</p><script>window.close()</script>`)
|
|
||||||
notify["status"] = "fail"
|
|
||||||
notify["message"] = "Failed to get rclone config"
|
|
||||||
logger.Error("Then get config error: ", zap.Error(err), zap.Any("name", "google_drive"))
|
|
||||||
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, v := range configs.Remotes {
|
|
||||||
cf, err := service.MyService.Storage().GetConfigByName(v)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("then get config by name error: ", zap.Error(err), zap.Any("name", v))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if cf["type"] == "drive" && cf["username"] == dmap["username"] {
|
|
||||||
c.String(200, `<p>The same configuration has been added</p><script>window.close()</script>`)
|
|
||||||
err := service.MyService.Storage().CheckAndMountByName(v)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("check and mount by name error: ", zap.Error(err), zap.Any("name", cf["username"]))
|
|
||||||
}
|
|
||||||
notify["status"] = "warn"
|
|
||||||
notify["message"] = "The same configuration has been added"
|
|
||||||
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(username) > 0 {
|
|
||||||
a := strings.Split(username, "@")
|
|
||||||
username = a[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
//username = fileutil.NameAccumulation(username, "/mnt")
|
|
||||||
username += "_google_drive_" + strconv.FormatInt(time.Now().Unix(), 10)
|
|
||||||
|
|
||||||
dmap["client_id"] = add.ClientID
|
|
||||||
dmap["client_secret"] = add.ClientSecret
|
|
||||||
dmap["scope"] = "drive"
|
|
||||||
dmap["mount_point"] = "/mnt/" + username
|
|
||||||
dmap["token"] = `{"access_token":"` + google_drive.AccessToken + `","token_type":"Bearer","refresh_token":"` + google_drive.RefreshToken + `","expiry":"` + currentDate + `T` + currentTime.Add(time.Hour*1).Add(time.Minute*50).Format("15:04:05") + `Z"}`
|
|
||||||
service.MyService.Storage().CreateConfig(dmap, username, "drive")
|
|
||||||
service.MyService.Storage().MountStorage("/mnt/"+username, username+":")
|
|
||||||
notify := make(map[string]interface{})
|
|
||||||
notify["status"] = "success"
|
|
||||||
notify["message"] = "Success"
|
|
||||||
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
|
|
||||||
} else if t == "Dropbox" {
|
|
||||||
add := dropbox.Addition{}
|
|
||||||
add.Code = c.Query("code")
|
|
||||||
if len(add.Code) == 0 {
|
|
||||||
c.String(200, `<p>Code cannot be empty</p><script>window.close()</script>`)
|
|
||||||
notify["status"] = "fail"
|
|
||||||
notify["message"] = "Code cannot be empty"
|
|
||||||
logger.Error("Then code is empty error: ", zap.String("code", add.Code), zap.Any("name", "dropbox"))
|
|
||||||
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
add.RootFolderID = ""
|
|
||||||
add.AppKey = dropbox.APPKEY
|
|
||||||
add.AppSecret = dropbox.APPSECRET
|
|
||||||
var dropbox dropbox.Dropbox
|
|
||||||
dropbox.Addition = add
|
|
||||||
err := dropbox.Init(c)
|
|
||||||
if err != nil {
|
|
||||||
c.String(200, `<p>Initialization failure:`+err.Error()+`</p><script>window.close()</script>`)
|
|
||||||
notify["status"] = "fail"
|
|
||||||
notify["message"] = "Initialization failure"
|
|
||||||
logger.Error("Then init error: ", zap.Error(err), zap.Any("name", "dropbox"))
|
|
||||||
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
username, err := dropbox.GetUserInfo(c)
|
|
||||||
if err != nil {
|
|
||||||
c.String(200, `<p>Failed to get user information:`+err.Error()+`</p><script>window.close()</script>`)
|
|
||||||
notify["status"] = "fail"
|
|
||||||
notify["message"] = "Failed to get user information"
|
|
||||||
logger.Error("Then get user information: ", zap.Error(err), zap.Any("name", "dropbox"))
|
|
||||||
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dmap := make(map[string]string)
|
|
||||||
dmap["username"] = username
|
|
||||||
|
|
||||||
configs, err := service.MyService.Storage().GetConfig()
|
|
||||||
if err != nil {
|
|
||||||
c.String(200, `<p>Failed to get rclone config:`+err.Error()+`</p><script>window.close()</script>`)
|
|
||||||
notify["status"] = "fail"
|
|
||||||
notify["message"] = "Failed to get rclone config"
|
|
||||||
logger.Error("Then get config error: ", zap.Error(err), zap.Any("name", "dropbox"))
|
|
||||||
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, v := range configs.Remotes {
|
|
||||||
cf, err := service.MyService.Storage().GetConfigByName(v)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("then get config by name error: ", zap.Error(err), zap.Any("name", v))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if cf["type"] == "dropbox" && cf["username"] == dmap["username"] {
|
|
||||||
c.String(200, `<p>The same configuration has been added</p><script>window.close()</script>`)
|
|
||||||
err := service.MyService.Storage().CheckAndMountByName(v)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("check and mount by name error: ", zap.Error(err), zap.Any("name", cf["username"]))
|
|
||||||
}
|
|
||||||
|
|
||||||
notify["status"] = "warn"
|
|
||||||
notify["message"] = "The same configuration has been added"
|
|
||||||
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(username) > 0 {
|
|
||||||
a := strings.Split(username, "@")
|
|
||||||
username = a[0]
|
|
||||||
}
|
|
||||||
username += "_dropbox_" + strconv.FormatInt(time.Now().Unix(), 10)
|
|
||||||
|
|
||||||
dmap["client_id"] = add.AppKey
|
|
||||||
dmap["client_secret"] = add.AppSecret
|
|
||||||
dmap["token"] = `{"access_token":"` + dropbox.AccessToken + `","token_type":"bearer","refresh_token":"` + dropbox.Addition.RefreshToken + `","expiry":"` + currentDate + `T` + currentTime.Add(time.Hour*3).Add(time.Minute*50).Format("15:04:05") + `.780385354Z"}`
|
|
||||||
dmap["mount_point"] = "/mnt/" + username
|
|
||||||
// data.SetValue(username, "type", "dropbox")
|
|
||||||
// data.SetValue(username, "client_id", add.AppKey)
|
|
||||||
// data.SetValue(username, "client_secret", add.AppSecret)
|
|
||||||
// data.SetValue(username, "mount_point", "/mnt/"+username)
|
|
||||||
|
|
||||||
// data.SetValue(username, "token", `{"access_token":"`+dropbox.AccessToken+`","token_type":"bearer","refresh_token":"`+dropbox.Addition.RefreshToken+`","expiry":"`+currentDate+`T`+currentTime.Add(time.Hour*3).Format("15:04:05")+`.780385354Z"}`)
|
|
||||||
// e = data.Save()
|
|
||||||
// if e != nil {
|
|
||||||
// c.String(200, `<p>保存配置失败:`+e.Error()+`</p>`)
|
|
||||||
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
service.MyService.Storage().CreateConfig(dmap, username, "dropbox")
|
|
||||||
service.MyService.Storage().MountStorage("/mnt/"+username, username+":")
|
|
||||||
|
|
||||||
notify["status"] = "success"
|
|
||||||
notify["message"] = "Success"
|
|
||||||
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.String(200, `<p>Just close the page</p><script>window.close()</script>`)
|
|
||||||
}
|
|
||||||
@ -1,143 +0,0 @@
|
|||||||
package v1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/drivers/dropbox"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/drivers/google_drive"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/model"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/utils/httper"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/service"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ListStorages(c *gin.Context) {
|
|
||||||
// var req model.PageReq
|
|
||||||
// if err := c.ShouldBind(&req); err != nil {
|
|
||||||
// c.JSON(common_err.SUCCESS, model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: err.Error()})
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// req.Validate()
|
|
||||||
|
|
||||||
//logger.Info("ListStorages", zap.Any("req", req))
|
|
||||||
//storages, total, err := service.MyService.Storage().GetStorages(req.Page, req.PerPage)
|
|
||||||
// if err != nil {
|
|
||||||
// c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: model.PageResp{
|
|
||||||
// Content: storages,
|
|
||||||
// Total: total,
|
|
||||||
// }})
|
|
||||||
r, err := service.MyService.Storage().GetStorages()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(r.MountPoints); i++ {
|
|
||||||
dataMap, err := service.MyService.Storage().GetConfigByName(r.MountPoints[i].Fs)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("GetConfigByName", zap.Any("err", err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if dataMap["type"] == "drive" {
|
|
||||||
r.MountPoints[i].Icon = google_drive.ICONURL
|
|
||||||
}
|
|
||||||
if dataMap["type"] == "dropbox" {
|
|
||||||
r.MountPoints[i].Icon = dropbox.ICONURL
|
|
||||||
}
|
|
||||||
r.MountPoints[i].Name = dataMap["username"]
|
|
||||||
}
|
|
||||||
list := []httper.MountPoint{}
|
|
||||||
|
|
||||||
for _, v := range r.MountPoints {
|
|
||||||
list = append(list, httper.MountPoint{
|
|
||||||
Fs: v.Fs,
|
|
||||||
Icon: v.Icon,
|
|
||||||
MountPoint: v.MountPoint,
|
|
||||||
Name: v.Name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: list})
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateStorage(c *gin.Context) {
|
|
||||||
var req model.Storage
|
|
||||||
if err := c.ShouldBind(&req); err != nil {
|
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := service.MyService.Storages().UpdateStorage(c, req); err != nil {
|
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
|
|
||||||
} else {
|
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: "success"})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteStorage(c *gin.Context) {
|
|
||||||
json := make(map[string]string)
|
|
||||||
c.ShouldBind(&json)
|
|
||||||
mountPoint := json["mount_point"]
|
|
||||||
if mountPoint == "" {
|
|
||||||
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: "mount_point is empty"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err := service.MyService.Storage().UnmountStorage(mountPoint)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
service.MyService.Storage().DeleteConfigByName(strings.ReplaceAll(mountPoint, "/mnt/", ""))
|
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: "success"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func DisableStorage(c *gin.Context) {
|
|
||||||
idStr := c.Query("id")
|
|
||||||
id, err := strconv.Atoi(idStr)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := service.MyService.Storages().DisableStorage(c, uint(id)); err != nil {
|
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: "success"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func EnableStorage(c *gin.Context) {
|
|
||||||
idStr := c.Query("id")
|
|
||||||
id, err := strconv.Atoi(idStr)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := service.MyService.Storages().EnableStorage(c, uint(id)); err != nil {
|
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: "success"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetStorage(c *gin.Context) {
|
|
||||||
|
|
||||||
// idStr := c.Query("id")
|
|
||||||
// id, err := strconv.Atoi(idStr)
|
|
||||||
// if err != nil {
|
|
||||||
// c.JSON(common_err.SUCCESS, model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: err.Error()})
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// storage, err := service.MyService.Storage().GetStorageById(uint(id))
|
|
||||||
// if err != nil {
|
|
||||||
// c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: storage})
|
|
||||||
}
|
|
||||||
154
service/fs.go
154
service/fs.go
@ -1,154 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/driver"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/model"
|
|
||||||
log "github.com/dsoprea/go-logging"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FsService interface {
|
|
||||||
FList(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error)
|
|
||||||
GetStorage(path string) (driver.Driver, error)
|
|
||||||
Link(ctx context.Context, path string, args model.LinkArgs) (*model.Link, model.Obj, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type fsService struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// the param named path of functions in this package is a mount path
|
|
||||||
// So, the purpose of this package is to convert mount path to actual path
|
|
||||||
// then pass the actual path to the op package
|
|
||||||
|
|
||||||
func (f *fsService) FList(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error) {
|
|
||||||
res, err := MyService.FsListService().FsList(ctx, path, refresh...)
|
|
||||||
if err != nil {
|
|
||||||
logger.Info("failed list", zap.Any("path", path), zap.Any("err", err))
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// func (f *fsService) Get(ctx context.Context, path string) (model.Obj, error) {
|
|
||||||
// res, err := get(ctx, path)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Errorf("failed get %s: %+v", path, err)
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// return res, nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (f *fsService) Link(ctx context.Context, path string, args model.LinkArgs) (*model.Link, model.Obj, error) {
|
|
||||||
res, file, err := MyService.FsLinkService().Link(ctx, path, args)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed link %s: %+v", path, err)
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return res, file, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// func (f *fsService) MakeDir(ctx context.Context, path string, lazyCache ...bool) error {
|
|
||||||
// err := makeDir(ctx, path, lazyCache...)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Errorf("failed make dir %s: %+v", path, err)
|
|
||||||
// }
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (f *fsService) Move(ctx context.Context, srcPath, dstDirPath string, lazyCache ...bool) error {
|
|
||||||
// err := move(ctx, srcPath, dstDirPath, lazyCache...)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Errorf("failed move %s to %s: %+v", srcPath, dstDirPath, err)
|
|
||||||
// }
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (f *fsService) Copy(ctx context.Context, srcObjPath, dstDirPath string, lazyCache ...bool) (bool, error) {
|
|
||||||
// res, err := _copy(ctx, srcObjPath, dstDirPath, lazyCache...)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Errorf("failed copy %s to %s: %+v", srcObjPath, dstDirPath, err)
|
|
||||||
// }
|
|
||||||
// return res, err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (f *fsService) Rename(ctx context.Context, srcPath, dstName string, lazyCache ...bool) error {
|
|
||||||
// err := rename(ctx, srcPath, dstName, lazyCache...)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Errorf("failed rename %s to %s: %+v", srcPath, dstName, err)
|
|
||||||
// }
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (f *fsService) Remove(ctx context.Context, path string) error {
|
|
||||||
// err := remove(ctx, path)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Errorf("failed remove %s: %+v", path, err)
|
|
||||||
// }
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func PutDirectly(ctx context.Context, dstDirPath string, file *model.FileStream, lazyCache ...bool) error {
|
|
||||||
// err := putDirectly(ctx, dstDirPath, file, lazyCache...)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Errorf("failed put %s: %+v", dstDirPath, err)
|
|
||||||
// }
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (f *fsService) PutAsTask(dstDirPath string, file *model.FileStream) error {
|
|
||||||
// err := putAsTask(dstDirPath, file)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Errorf("failed put %s: %+v", dstDirPath, err)
|
|
||||||
// }
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (f *fsService) GetStorage(path string) (driver.Driver, error) {
|
|
||||||
storageDriver, _, err := MyService.StoragePath().GetStorageAndActualPath(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return storageDriver, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// func (f *fsService) Other(ctx context.Context, args model.FsOtherArgs) (interface{}, error) {
|
|
||||||
// res, err := other(ctx, args)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Errorf("failed remove %s: %+v", args.Path, err)
|
|
||||||
// }
|
|
||||||
// return res, err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func get(ctx context.Context, path string) (model.Obj, error) {
|
|
||||||
// path = utils.FixAndCleanPath(path)
|
|
||||||
// // maybe a virtual file
|
|
||||||
// if path != "/" {
|
|
||||||
// virtualFiles := op.GetStorageVirtualFilesByPath(stdpath.Dir(path))
|
|
||||||
// for _, f := range virtualFiles {
|
|
||||||
// if f.GetName() == stdpath.Base(path) {
|
|
||||||
// return f, nil
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// storage, actualPath, err := op.GetStorageAndActualPath(path)
|
|
||||||
// if err != nil {
|
|
||||||
// // if there are no storage prefix with path, maybe root folder
|
|
||||||
// if path == "/" {
|
|
||||||
// return &model.Object{
|
|
||||||
// Name: "root",
|
|
||||||
// Size: 0,
|
|
||||||
// Modified: time.Time{},
|
|
||||||
// IsFolder: true,
|
|
||||||
// }, nil
|
|
||||||
// }
|
|
||||||
// return nil, errors.WithMessage(err, "failed get storage")
|
|
||||||
// }
|
|
||||||
// return op.Get(ctx, storage, actualPath)
|
|
||||||
// }
|
|
||||||
|
|
||||||
func NewFsService() FsService {
|
|
||||||
return &fsService{}
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/op"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/model"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FsLinkService interface {
|
|
||||||
Link(ctx context.Context, path string, args model.LinkArgs) (*model.Link, model.Obj, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type fsLinkService struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fsLinkService) Link(ctx context.Context, path string, args model.LinkArgs) (*model.Link, model.Obj, error) {
|
|
||||||
storage, actualPath, err := MyService.StoragePath().GetStorageAndActualPath(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.WithMessage(err, "failed get storage")
|
|
||||||
}
|
|
||||||
return op.Link(ctx, storage, actualPath, args)
|
|
||||||
}
|
|
||||||
func NewFsLinkService() FsLinkService {
|
|
||||||
return &fsLinkService{}
|
|
||||||
}
|
|
||||||
@ -1,198 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
stdpath "path"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/driver"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/op"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/model"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/singleflight"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/utils"
|
|
||||||
"github.com/Xhofe/go-cache"
|
|
||||||
|
|
||||||
log "github.com/dsoprea/go-logging"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FsListService interface {
|
|
||||||
FsList(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error)
|
|
||||||
Key(storage driver.Driver, path string) string
|
|
||||||
Get(ctx context.Context, storage driver.Driver, path string) (model.Obj, error)
|
|
||||||
GetUnwrap(ctx context.Context, storage driver.Driver, path string) (model.Obj, error)
|
|
||||||
List(ctx context.Context, storage driver.Driver, path string, args model.ListArgs, refresh ...bool) ([]model.Obj, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type fsListService struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
var listCache = cache.NewMemCache(cache.WithShards[[]model.Obj](64))
|
|
||||||
var listG singleflight.Group[[]model.Obj]
|
|
||||||
|
|
||||||
// List files
|
|
||||||
func (fl *fsListService) FsList(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error) {
|
|
||||||
|
|
||||||
virtualFiles := MyService.Storages().GetStorageVirtualFilesByPath(path)
|
|
||||||
storage, actualPath, err := MyService.StoragePath().GetStorageAndActualPath(path)
|
|
||||||
if err != nil && len(virtualFiles) == 0 {
|
|
||||||
return nil, errors.WithMessage(err, "failed get storage")
|
|
||||||
}
|
|
||||||
|
|
||||||
var _objs []model.Obj
|
|
||||||
if storage != nil {
|
|
||||||
_objs, err = fl.List(ctx, storage, actualPath, model.ListArgs{
|
|
||||||
ReqPath: path,
|
|
||||||
}, refresh...)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("%+v", err)
|
|
||||||
if len(virtualFiles) == 0 {
|
|
||||||
return nil, errors.WithMessage(err, "failed get objs")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
om := model.NewObjMerge()
|
|
||||||
|
|
||||||
objs := om.Merge(virtualFiles, _objs...)
|
|
||||||
return objs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fl *fsListService) Key(storage driver.Driver, path string) string {
|
|
||||||
return stdpath.Join(storage.GetStorage().MountPath, utils.FixAndCleanPath(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get object from list of files
|
|
||||||
func (fl *fsListService) Get(ctx context.Context, storage driver.Driver, path string) (model.Obj, error) {
|
|
||||||
path = utils.FixAndCleanPath(path)
|
|
||||||
logger.Info("get", zap.String("path", path))
|
|
||||||
|
|
||||||
// is root folder
|
|
||||||
if utils.PathEqual(path, "/") {
|
|
||||||
var rootObj model.Obj
|
|
||||||
switch r := storage.GetAddition().(type) {
|
|
||||||
case driver.IRootId:
|
|
||||||
rootObj = &model.Object{
|
|
||||||
ID: r.GetRootId(),
|
|
||||||
Name: op.RootName,
|
|
||||||
Size: 0,
|
|
||||||
Modified: storage.GetStorage().Modified,
|
|
||||||
IsFolder: true,
|
|
||||||
}
|
|
||||||
case driver.IRootPath:
|
|
||||||
rootObj = &model.Object{
|
|
||||||
Path: r.GetRootPath(),
|
|
||||||
Name: op.RootName,
|
|
||||||
Size: 0,
|
|
||||||
Modified: storage.GetStorage().Modified,
|
|
||||||
IsFolder: true,
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if storage, ok := storage.(driver.Getter); ok {
|
|
||||||
obj, err := storage.GetRoot(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "failed get root obj")
|
|
||||||
}
|
|
||||||
rootObj = obj
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rootObj == nil {
|
|
||||||
return nil, errors.Errorf("please implement IRootPath or IRootId or Getter method")
|
|
||||||
}
|
|
||||||
return &model.ObjWrapName{
|
|
||||||
Name: op.RootName,
|
|
||||||
Obj: rootObj,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// not root folder
|
|
||||||
dir, name := stdpath.Split(path)
|
|
||||||
files, err := fl.List(ctx, storage, dir, model.ListArgs{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "failed get parent list")
|
|
||||||
}
|
|
||||||
for _, f := range files {
|
|
||||||
// TODO maybe copy obj here
|
|
||||||
if f.GetName() == name {
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.Info("cant find obj with name", zap.Any("name", name))
|
|
||||||
return nil, errors.WithStack(errors.New("object not found"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fl *fsListService) GetUnwrap(ctx context.Context, storage driver.Driver, path string) (model.Obj, error) {
|
|
||||||
obj, err := fl.Get(ctx, storage, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return model.UnwrapObjs(obj), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// List files in storage, not contains virtual file
|
|
||||||
func (fl *fsListService) List(ctx context.Context, storage driver.Driver, path string, args model.ListArgs, refresh ...bool) ([]model.Obj, error) {
|
|
||||||
if storage.Config().CheckStatus && storage.GetStorage().Status != op.WORK {
|
|
||||||
return nil, errors.Errorf("storage not init: %s", storage.GetStorage().Status)
|
|
||||||
}
|
|
||||||
path = utils.FixAndCleanPath(path)
|
|
||||||
logger.Info("op.List", zap.Any("path", path))
|
|
||||||
key := fl.Key(storage, path)
|
|
||||||
if !utils.IsBool(refresh...) {
|
|
||||||
if files, ok := listCache.Get(key); ok {
|
|
||||||
logger.Info("op.List", zap.Any("use cache", path))
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dir, err := fl.GetUnwrap(ctx, storage, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithMessage(err, "failed get dir")
|
|
||||||
}
|
|
||||||
logger.Info("op.List", zap.Any("dir", dir))
|
|
||||||
if !dir.IsDir() {
|
|
||||||
return nil, errors.WithStack(errors.New("not a folder"))
|
|
||||||
}
|
|
||||||
objs, err, _ := listG.Do(key, func() ([]model.Obj, error) {
|
|
||||||
files, err := storage.List(ctx, dir, args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "failed to list objs")
|
|
||||||
}
|
|
||||||
// set path
|
|
||||||
for _, f := range files {
|
|
||||||
if s, ok := f.(model.SetPath); ok && f.GetPath() == "" && dir.GetPath() != "" {
|
|
||||||
s.SetPath(stdpath.Join(dir.GetPath(), f.GetName()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// warp obj name
|
|
||||||
model.WrapObjsName(files)
|
|
||||||
// call hooks
|
|
||||||
go func(reqPath string, files []model.Obj) {
|
|
||||||
for _, hook := range op.ObjsUpdateHooks {
|
|
||||||
hook(args.ReqPath, files)
|
|
||||||
}
|
|
||||||
}(args.ReqPath, files)
|
|
||||||
|
|
||||||
// sort objs
|
|
||||||
if storage.Config().LocalSort {
|
|
||||||
model.SortFiles(files, storage.GetStorage().OrderBy, storage.GetStorage().OrderDirection)
|
|
||||||
}
|
|
||||||
model.ExtractFolder(files, storage.GetStorage().ExtractFolder)
|
|
||||||
|
|
||||||
if !storage.Config().NoCache {
|
|
||||||
if len(files) > 0 {
|
|
||||||
logger.Info("set cache", zap.Any("key", key), zap.Any("files", files))
|
|
||||||
listCache.Set(key, files, cache.WithEx[[]model.Obj](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
|
|
||||||
} else {
|
|
||||||
logger.Info("del cache", zap.Any("key", key))
|
|
||||||
listCache.Del(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
})
|
|
||||||
return objs, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFsListService() FsListService {
|
|
||||||
return &fsListService{}
|
|
||||||
}
|
|
||||||
@ -39,12 +39,7 @@ type Repository interface {
|
|||||||
Rely() RelyService
|
Rely() RelyService
|
||||||
Shares() SharesService
|
Shares() SharesService
|
||||||
System() SystemService
|
System() SystemService
|
||||||
Storage() StorageService
|
|
||||||
Storages() StoragesService
|
|
||||||
StoragePath() StoragePathService
|
|
||||||
FsListService() FsListService
|
|
||||||
FsLinkService() FsLinkService
|
|
||||||
FsService() FsService
|
|
||||||
MessageBus() *message_bus.ClientWithResponses
|
MessageBus() *message_bus.ClientWithResponses
|
||||||
Peer() PeerService
|
Peer() PeerService
|
||||||
Other() OtherService
|
Other() OtherService
|
||||||
@ -57,43 +52,56 @@ func NewService(db *gorm.DB, RuntimePath string) Repository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &store{
|
return &store{
|
||||||
casa: NewCasaService(),
|
casa: NewCasaService(),
|
||||||
connections: NewConnectionsService(db),
|
connections: NewConnectionsService(db),
|
||||||
gateway: gatewayManagement,
|
gateway: gatewayManagement,
|
||||||
notify: NewNotifyService(db),
|
notify: NewNotifyService(db),
|
||||||
rely: NewRelyService(db),
|
rely: NewRelyService(db),
|
||||||
system: NewSystemService(),
|
system: NewSystemService(),
|
||||||
health: NewHealthService(),
|
health: NewHealthService(),
|
||||||
shares: NewSharesService(db),
|
shares: NewSharesService(db),
|
||||||
storage: NewStorageService(),
|
storage: NewStorageService(),
|
||||||
storages: NewStoragesService(),
|
|
||||||
storage_path: NewStoragePathService(),
|
peer: NewPeerService(db),
|
||||||
fs_list: NewFsListService(),
|
other: NewOtherService(),
|
||||||
fs_link: NewFsLinkService(),
|
casa: NewCasaService(),
|
||||||
fs: NewFsService(),
|
|
||||||
peer: NewPeerService(db),
|
gateway: gatewayManagement,
|
||||||
other: NewOtherService(),
|
notify: NewNotifyService(db),
|
||||||
|
rely: NewRelyService(db),
|
||||||
|
system: NewSystemService(),
|
||||||
|
health: NewHealthService(),
|
||||||
|
shares: NewSharesService(db),
|
||||||
|
|
||||||
|
other: NewOtherService(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type store struct {
|
type store struct {
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
casa CasaService
|
casa CasaService
|
||||||
notify NotifyServer
|
notify NotifyServer
|
||||||
rely RelyService
|
rely RelyService
|
||||||
system SystemService
|
system SystemService
|
||||||
shares SharesService
|
shares SharesService
|
||||||
connections ConnectionsService
|
connections ConnectionsService
|
||||||
gateway external.ManagementService
|
gateway external.ManagementService
|
||||||
storage StorageService
|
storage StorageService
|
||||||
storages StoragesService
|
|
||||||
storage_path StoragePathService
|
health HealthService
|
||||||
fs_list FsListService
|
peer PeerService
|
||||||
fs_link FsLinkService
|
other OtherService
|
||||||
fs FsService
|
db *gorm.DB
|
||||||
health HealthService
|
casa CasaService
|
||||||
peer PeerService
|
notify NotifyServer
|
||||||
other OtherService
|
rely RelyService
|
||||||
|
system SystemService
|
||||||
|
shares SharesService
|
||||||
|
connections ConnectionsService
|
||||||
|
gateway external.ManagementService
|
||||||
|
|
||||||
|
health HealthService
|
||||||
|
other OtherService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *store) Peer() PeerService {
|
func (c *store) Peer() PeerService {
|
||||||
@ -104,29 +112,6 @@ func (c *store) Other() OtherService {
|
|||||||
return c.other
|
return c.other
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *store) FsLinkService() FsLinkService {
|
|
||||||
return c.fs_link
|
|
||||||
}
|
|
||||||
func (c *store) FsService() FsService {
|
|
||||||
return c.fs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *store) FsListService() FsListService {
|
|
||||||
return c.fs_list
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *store) StoragePath() StoragePathService {
|
|
||||||
return c.storage_path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *store) Storages() StoragesService {
|
|
||||||
return c.storages
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *store) Storage() StorageService {
|
|
||||||
return c.storage
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *store) Gateway() external.ManagementService {
|
func (c *store) Gateway() external.ManagementService {
|
||||||
return c.gateway
|
return c.gateway
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,116 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/utils/httper"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StorageService interface {
|
|
||||||
MountStorage(mountPoint, fs string) error
|
|
||||||
UnmountStorage(mountPoint string) error
|
|
||||||
GetStorages() (httper.MountList, error)
|
|
||||||
CreateConfig(data map[string]string, name string, t string) error
|
|
||||||
CheckAndMountByName(name string) error
|
|
||||||
CheckAndMountAll() error
|
|
||||||
GetConfigByName(name string) (map[string]string, error)
|
|
||||||
DeleteConfigByName(name string) error
|
|
||||||
GetConfig() (httper.RemotesResult, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type storageStruct struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *storageStruct) MountStorage(mountPoint, fs string) error {
|
|
||||||
file.IsNotExistMkDir(mountPoint)
|
|
||||||
return httper.Mount(mountPoint, fs)
|
|
||||||
}
|
|
||||||
func (s *storageStruct) UnmountStorage(mountPoint string) error {
|
|
||||||
err := httper.Unmount(mountPoint)
|
|
||||||
if err == nil {
|
|
||||||
dir, _ := ioutil.ReadDir(mountPoint)
|
|
||||||
|
|
||||||
if len(dir) == 0 {
|
|
||||||
file.RMDir(mountPoint)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
func (s *storageStruct) GetStorages() (httper.MountList, error) {
|
|
||||||
return httper.GetMountList()
|
|
||||||
}
|
|
||||||
func (s *storageStruct) CreateConfig(data map[string]string, name string, t string) error {
|
|
||||||
httper.CreateConfig(data, name, t)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (s *storageStruct) CheckAndMountByName(name string) error {
|
|
||||||
storages, _ := MyService.Storage().GetStorages()
|
|
||||||
currentRemote, _ := httper.GetConfigByName(name)
|
|
||||||
mountPoint := currentRemote["mount_point"]
|
|
||||||
isMount := false
|
|
||||||
for _, v := range storages.MountPoints {
|
|
||||||
if v.MountPoint == mountPoint {
|
|
||||||
isMount = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !isMount {
|
|
||||||
return MyService.Storage().MountStorage(mountPoint, name+":")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (s *storageStruct) CheckAndMountAll() error {
|
|
||||||
storages, err := MyService.Storage().GetStorages()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Info("when CheckAndMountAll storages", zap.Any("storages", storages))
|
|
||||||
section, err := httper.GetAllConfigName()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Info("when CheckAndMountAll section", zap.Any("section", section))
|
|
||||||
for _, v := range section.Remotes {
|
|
||||||
currentRemote, _ := httper.GetConfigByName(v)
|
|
||||||
mountPoint := currentRemote["mount_point"]
|
|
||||||
if len(mountPoint) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
isMount := false
|
|
||||||
for _, v := range storages.MountPoints {
|
|
||||||
if v.MountPoint == mountPoint {
|
|
||||||
isMount = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !isMount {
|
|
||||||
logger.Info("when CheckAndMountAll MountStorage", zap.String("mountPoint", mountPoint), zap.String("fs", v))
|
|
||||||
err := MyService.Storage().MountStorage(mountPoint, v+":")
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("when CheckAndMountAll then", zap.String("mountPoint", mountPoint), zap.String("fs", v), zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *storageStruct) GetConfigByName(name string) (map[string]string, error) {
|
|
||||||
return httper.GetConfigByName(name)
|
|
||||||
}
|
|
||||||
func (s *storageStruct) DeleteConfigByName(name string) error {
|
|
||||||
return httper.DeleteConfigByName(name)
|
|
||||||
}
|
|
||||||
func (s *storageStruct) GetConfig() (httper.RemotesResult, error) {
|
|
||||||
section, err := httper.GetAllConfigName()
|
|
||||||
if err != nil {
|
|
||||||
return httper.RemotesResult{}, err
|
|
||||||
}
|
|
||||||
return section, nil
|
|
||||||
}
|
|
||||||
func NewStorageService() StorageService {
|
|
||||||
return &storageStruct{}
|
|
||||||
}
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/driver"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/utils"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StoragePathService interface {
|
|
||||||
GetStorageAndActualPath(rawPath string) (storage driver.Driver, actualPath string, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type storagePathStruct struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *storagePathStruct) GetStorageAndActualPath(rawPath string) (storage driver.Driver, actualPath string, err error) {
|
|
||||||
rawPath = utils.FixAndCleanPath(rawPath)
|
|
||||||
storage = MyService.Storages().GetBalancedStorage(rawPath)
|
|
||||||
if storage == nil {
|
|
||||||
err = errors.Errorf("can't find storage with rawPath: %s", rawPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.Info("use storage", zap.Any("storage mount path", storage.GetStorage().MountPath))
|
|
||||||
mountPath := utils.GetActualMountPath(storage.GetStorage().MountPath)
|
|
||||||
actualPath = utils.FixAndCleanPath(strings.TrimPrefix(rawPath, mountPath))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
func NewStoragePathService() StoragePathService {
|
|
||||||
return &storagePathStruct{}
|
|
||||||
}
|
|
||||||
@ -1,398 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/utils"
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS/pkg/generic_sync"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS/model"
|
|
||||||
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/driver"
|
|
||||||
"github.com/IceWhaleTech/CasaOS/internal/op"
|
|
||||||
mapset "github.com/deckarep/golang-set/v2"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StoragesService interface {
|
|
||||||
HasStorage(mountPath string) bool
|
|
||||||
CreateStorage(ctx context.Context, storage model.Storage) (uint, error)
|
|
||||||
LoadStorage(ctx context.Context, storage model.Storage) error
|
|
||||||
EnableStorage(ctx context.Context, id uint) error
|
|
||||||
DisableStorage(ctx context.Context, id uint) error
|
|
||||||
UpdateStorage(ctx context.Context, storage model.Storage) error
|
|
||||||
DeleteStorageById(ctx context.Context, id uint) error
|
|
||||||
MustSaveDriverStorage(driver driver.Driver) error
|
|
||||||
GetStorageVirtualFilesByPath(prefix string) []model.Obj
|
|
||||||
initStorage(ctx context.Context, storage model.Storage, storageDriver driver.Driver, setMountPath func(d driver.Driver, ctx context.Context) string) (err error)
|
|
||||||
InitStorages()
|
|
||||||
GetBalancedStorage(path string) driver.Driver
|
|
||||||
}
|
|
||||||
|
|
||||||
type storagesStruct struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Although the driver type is stored,
|
|
||||||
// there is a storage in each driver,
|
|
||||||
// so it should actually be a storage, just wrapped by the driver
|
|
||||||
var storagesMap generic_sync.MapOf[string, driver.Driver]
|
|
||||||
|
|
||||||
func GetAllStorages() []driver.Driver {
|
|
||||||
return storagesMap.Values()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *storagesStruct) HasStorage(mountPath string) bool {
|
|
||||||
return storagesMap.Has(utils.FixAndCleanPath(mountPath))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetStorageByMountPath(mountPath string) (driver.Driver, error) {
|
|
||||||
mountPath = utils.FixAndCleanPath(mountPath)
|
|
||||||
storageDriver, ok := storagesMap.Load(mountPath)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.Errorf("no mount path for an storage is: %s", mountPath)
|
|
||||||
}
|
|
||||||
return storageDriver, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateStorage Save the storage to database so storage can get an id
|
|
||||||
// then instantiate corresponding driver and save it in memory
|
|
||||||
func (s *storagesStruct) CreateStorage(ctx context.Context, storage model.Storage) (uint, error) {
|
|
||||||
storage.Modified = time.Now()
|
|
||||||
storage.MountPath = utils.FixAndCleanPath(storage.MountPath)
|
|
||||||
var err error
|
|
||||||
// check driver first
|
|
||||||
driverName := storage.Driver
|
|
||||||
driverNew, err := op.GetDriverNew(driverName)
|
|
||||||
if err != nil {
|
|
||||||
return 0, errors.WithMessage(err, "failed get driver new")
|
|
||||||
}
|
|
||||||
storageDriver := driverNew()
|
|
||||||
// // insert storage to database
|
|
||||||
// err = MyService.Storage().CreateStorage(&storage)
|
|
||||||
// if err != nil {
|
|
||||||
|
|
||||||
// return storage.ID, errors.WithMessage(err, "failed create storage in database")
|
|
||||||
// }
|
|
||||||
// already has an id
|
|
||||||
err = s.initStorage(ctx, storage, storageDriver, func(d driver.Driver, ctx context.Context) string {
|
|
||||||
u, _ := d.GetUserInfo(ctx)
|
|
||||||
if len(u) > 0 {
|
|
||||||
a := strings.Split(u, "@")
|
|
||||||
u = a[0]
|
|
||||||
}
|
|
||||||
return u
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
s.DeleteStorageById(ctx, storage.ID)
|
|
||||||
return storage.ID, errors.Wrap(err, "failed init storage")
|
|
||||||
}
|
|
||||||
|
|
||||||
go op.CallStorageHooks("add", storageDriver)
|
|
||||||
|
|
||||||
logger.Error("storage created", zap.Any("storage", storageDriver))
|
|
||||||
return storage.ID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadStorage load exist storage in db to memory
|
|
||||||
func (s *storagesStruct) LoadStorage(ctx context.Context, storage model.Storage) error {
|
|
||||||
storage.MountPath = utils.FixAndCleanPath(storage.MountPath)
|
|
||||||
// check driver first
|
|
||||||
driverName := storage.Driver
|
|
||||||
driverNew, err := op.GetDriverNew(driverName)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithMessage(err, "failed get driver new")
|
|
||||||
}
|
|
||||||
storageDriver := driverNew()
|
|
||||||
|
|
||||||
err = s.initStorage(ctx, storage, storageDriver, nil)
|
|
||||||
go op.CallStorageHooks("add", storageDriver)
|
|
||||||
logger.Info("storage created", zap.Any("storage", storageDriver))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// initStorage initialize the driver and store to storagesMap
|
|
||||||
func (s *storagesStruct) initStorage(ctx context.Context, storage model.Storage, storageDriver driver.Driver, setMountPath func(d driver.Driver, ctx context.Context) string) (err error) {
|
|
||||||
storageDriver.SetStorage(storage)
|
|
||||||
driverStorage := storageDriver.GetStorage()
|
|
||||||
|
|
||||||
// Unmarshal Addition
|
|
||||||
|
|
||||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
|
||||||
|
|
||||||
err = json.UnmarshalFromString(driverStorage.Addition, storageDriver.GetAddition())
|
|
||||||
if err == nil {
|
|
||||||
err = storageDriver.Init(ctx)
|
|
||||||
}
|
|
||||||
if setMountPath != nil {
|
|
||||||
driverStorage.MountPath += "_" + setMountPath(storageDriver, ctx)
|
|
||||||
|
|
||||||
}
|
|
||||||
if s.HasStorage(driverStorage.MountPath) {
|
|
||||||
return errors.New("mount path already exists")
|
|
||||||
}
|
|
||||||
storageDriver.SetStorage(*driverStorage)
|
|
||||||
storagesMap.Store(driverStorage.MountPath, storageDriver)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
driverStorage.SetStatus(err.Error())
|
|
||||||
err = errors.Wrap(err, "failed init storage")
|
|
||||||
} else {
|
|
||||||
driverStorage.SetStatus(op.WORK)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.MustSaveDriverStorage(storageDriver)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *storagesStruct) EnableStorage(ctx context.Context, id uint) error {
|
|
||||||
// storage, err := MyService.Storage().GetStorageById(id)
|
|
||||||
// if err != nil {
|
|
||||||
// return errors.WithMessage(err, "failed get storage")
|
|
||||||
// }
|
|
||||||
// if !storage.Disabled {
|
|
||||||
// return errors.Errorf("this storage have enabled")
|
|
||||||
// }
|
|
||||||
// storage.Disabled = false
|
|
||||||
// err = MyService.Storage().UpdateStorage(storage)
|
|
||||||
// if err != nil {
|
|
||||||
// return errors.WithMessage(err, "failed update storage in db")
|
|
||||||
// }
|
|
||||||
// err = s.LoadStorage(ctx, *storage)
|
|
||||||
// if err != nil {
|
|
||||||
// return errors.WithMessage(err, "failed load storage")
|
|
||||||
// }
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *storagesStruct) DisableStorage(ctx context.Context, id uint) error {
|
|
||||||
// storage, err := MyService.Storage().GetStorageById(id)
|
|
||||||
// if err != nil {
|
|
||||||
// return errors.WithMessage(err, "failed get storage")
|
|
||||||
// }
|
|
||||||
// if storage.Disabled {
|
|
||||||
// return errors.Errorf("this storage have disabled")
|
|
||||||
// }
|
|
||||||
// storageDriver, err := GetStorageByMountPath(storage.MountPath)
|
|
||||||
// if err != nil {
|
|
||||||
// return errors.WithMessage(err, "failed get storage driver")
|
|
||||||
// }
|
|
||||||
// // drop the storage in the driver
|
|
||||||
// if err := storageDriver.Drop(ctx); err != nil {
|
|
||||||
// return errors.Wrap(err, "failed drop storage")
|
|
||||||
// }
|
|
||||||
// // delete the storage in the memory
|
|
||||||
// storage.Disabled = true
|
|
||||||
// err = MyService.Storage().UpdateStorage(storage)
|
|
||||||
// if err != nil {
|
|
||||||
// return errors.WithMessage(err, "failed update storage in db")
|
|
||||||
// }
|
|
||||||
// storagesMap.Delete(storage.MountPath)
|
|
||||||
// go op.CallStorageHooks("del", storageDriver)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateStorage update storage
|
|
||||||
// get old storage first
|
|
||||||
// drop the storage then reinitialize
|
|
||||||
func (s *storagesStruct) UpdateStorage(ctx context.Context, storage model.Storage) error {
|
|
||||||
// oldStorage, err := MyService.Storage().GetStorageById(storage.ID)
|
|
||||||
// if err != nil {
|
|
||||||
// return errors.WithMessage(err, "failed get old storage")
|
|
||||||
// }
|
|
||||||
// if oldStorage.Driver != storage.Driver {
|
|
||||||
// return errors.Errorf("driver cannot be changed")
|
|
||||||
// }
|
|
||||||
// storage.Modified = time.Now()
|
|
||||||
// storage.MountPath = utils.FixAndCleanPath(storage.MountPath)
|
|
||||||
// err = MyService.Storage().UpdateStorage(&storage)
|
|
||||||
// if err != nil {
|
|
||||||
// return errors.WithMessage(err, "failed update storage in database")
|
|
||||||
// }
|
|
||||||
// if storage.Disabled {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
// storageDriver, err := GetStorageByMountPath(oldStorage.MountPath)
|
|
||||||
// if oldStorage.MountPath != storage.MountPath {
|
|
||||||
// // mount path renamed, need to drop the storage
|
|
||||||
// storagesMap.Delete(oldStorage.MountPath)
|
|
||||||
// }
|
|
||||||
// if err != nil {
|
|
||||||
// return errors.WithMessage(err, "failed get storage driver")
|
|
||||||
// }
|
|
||||||
// err = storageDriver.Drop(ctx)
|
|
||||||
// if err != nil {
|
|
||||||
// return errors.Wrapf(err, "failed drop storage")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// err = s.initStorage(ctx, storage, storageDriver, nil)
|
|
||||||
// go op.CallStorageHooks("update", storageDriver)
|
|
||||||
|
|
||||||
// logger.Info("storage updated", zap.Any("storage", storageDriver))
|
|
||||||
//return err
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *storagesStruct) DeleteStorageById(ctx context.Context, id uint) error {
|
|
||||||
// storage, err := MyService.Storage().GetStorageById(id)
|
|
||||||
// if err != nil {
|
|
||||||
// return errors.WithMessage(err, "failed get storage")
|
|
||||||
// }
|
|
||||||
// if !storage.Disabled {
|
|
||||||
// storageDriver, err := GetStorageByMountPath(storage.MountPath)
|
|
||||||
// if err == nil {
|
|
||||||
// // drop the storage in the driver
|
|
||||||
// if err := storageDriver.Drop(ctx); err != nil {
|
|
||||||
// return errors.Wrapf(err, "failed drop storage")
|
|
||||||
// }
|
|
||||||
// // delete the storage in the memory
|
|
||||||
// storagesMap.Delete(storage.MountPath)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// go op.CallStorageHooks("del", storageDriver)
|
|
||||||
// }
|
|
||||||
// // delete the storage in the database
|
|
||||||
// if err := MyService.Storage().DeleteStorageById(id); err != nil {
|
|
||||||
// return errors.WithMessage(err, "failed delete storage in database")
|
|
||||||
// }
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustSaveDriverStorage call from specific driver
|
|
||||||
func (s *storagesStruct) MustSaveDriverStorage(driver driver.Driver) error {
|
|
||||||
err := saveDriverStorage(driver)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("failed save driver storage", zap.Any("err", err))
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveDriverStorage(driver driver.Driver) error {
|
|
||||||
// storage := driver.GetStorage()
|
|
||||||
// addition := driver.GetAddition()
|
|
||||||
|
|
||||||
// var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
|
||||||
|
|
||||||
// str, err := json.MarshalToString(addition)
|
|
||||||
// if err != nil {
|
|
||||||
// return errors.Wrap(err, "error while marshal addition")
|
|
||||||
// }
|
|
||||||
// storage.Addition = str
|
|
||||||
// err = MyService.Storage().UpdateStorage(storage)
|
|
||||||
// if err != nil {
|
|
||||||
// return errors.WithMessage(err, "failed update storage in database")
|
|
||||||
// }
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getStoragesByPath get storage by longest match path, contains balance storage.
|
|
||||||
// for example, there is /a/b,/a/c,/a/d/e,/a/d/e.balance
|
|
||||||
// getStoragesByPath(/a/d/e/f) => /a/d/e,/a/d/e.balance
|
|
||||||
func getStoragesByPath(path string) []driver.Driver {
|
|
||||||
storages := make([]driver.Driver, 0)
|
|
||||||
curSlashCount := 0
|
|
||||||
storagesMap.Range(func(mountPath string, value driver.Driver) bool {
|
|
||||||
mountPath = utils.GetActualMountPath(mountPath)
|
|
||||||
// is this path
|
|
||||||
if utils.IsSubPath(mountPath, path) {
|
|
||||||
slashCount := strings.Count(utils.PathAddSeparatorSuffix(mountPath), "/")
|
|
||||||
// not the longest match
|
|
||||||
if slashCount > curSlashCount {
|
|
||||||
storages = storages[:0]
|
|
||||||
curSlashCount = slashCount
|
|
||||||
}
|
|
||||||
if slashCount == curSlashCount {
|
|
||||||
storages = append(storages, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
// make sure the order is the same for same input
|
|
||||||
sort.Slice(storages, func(i, j int) bool {
|
|
||||||
return storages[i].GetStorage().MountPath < storages[j].GetStorage().MountPath
|
|
||||||
})
|
|
||||||
return storages
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetStorageVirtualFilesByPath Obtain the virtual file generated by the storage according to the path
|
|
||||||
// for example, there are: /a/b,/a/c,/a/d/e,/a/b.balance1,/av
|
|
||||||
// GetStorageVirtualFilesByPath(/a) => b,c,d
|
|
||||||
func (s *storagesStruct) GetStorageVirtualFilesByPath(prefix string) []model.Obj {
|
|
||||||
files := make([]model.Obj, 0)
|
|
||||||
storages := storagesMap.Values()
|
|
||||||
sort.Slice(storages, func(i, j int) bool {
|
|
||||||
if storages[i].GetStorage().Order == storages[j].GetStorage().Order {
|
|
||||||
return storages[i].GetStorage().MountPath < storages[j].GetStorage().MountPath
|
|
||||||
}
|
|
||||||
return storages[i].GetStorage().Order < storages[j].GetStorage().Order
|
|
||||||
})
|
|
||||||
|
|
||||||
prefix = utils.FixAndCleanPath(prefix)
|
|
||||||
set := mapset.NewSet[string]()
|
|
||||||
for _, v := range storages {
|
|
||||||
mountPath := utils.GetActualMountPath(v.GetStorage().MountPath)
|
|
||||||
// Exclude prefix itself and non prefix
|
|
||||||
if len(prefix) >= len(mountPath) || !utils.IsSubPath(prefix, mountPath) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name := strings.SplitN(strings.TrimPrefix(mountPath[len(prefix):], "/"), "/", 2)[0]
|
|
||||||
if set.Add(name) {
|
|
||||||
files = append(files, &model.Object{
|
|
||||||
Name: name,
|
|
||||||
Size: 0,
|
|
||||||
Modified: v.GetStorage().Modified,
|
|
||||||
IsFolder: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files
|
|
||||||
}
|
|
||||||
|
|
||||||
var balanceMap generic_sync.MapOf[string, int]
|
|
||||||
|
|
||||||
// GetBalancedStorage get storage by path
|
|
||||||
func (s *storagesStruct) GetBalancedStorage(path string) driver.Driver {
|
|
||||||
path = utils.FixAndCleanPath(path)
|
|
||||||
storages := getStoragesByPath(path)
|
|
||||||
storageNum := len(storages)
|
|
||||||
switch storageNum {
|
|
||||||
case 0:
|
|
||||||
return nil
|
|
||||||
case 1:
|
|
||||||
return storages[0]
|
|
||||||
default:
|
|
||||||
virtualPath := utils.GetActualMountPath(storages[0].GetStorage().MountPath)
|
|
||||||
i, _ := balanceMap.LoadOrStore(virtualPath, 0)
|
|
||||||
i = (i + 1) % storageNum
|
|
||||||
balanceMap.Store(virtualPath, i)
|
|
||||||
return storages[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (s *storagesStruct) InitStorages() {
|
|
||||||
// storages, err := MyService.Storage().GetEnabledStorages()
|
|
||||||
// if err != nil {
|
|
||||||
// logger.Error("failed get enabled storages", zap.Any("err", err))
|
|
||||||
// }
|
|
||||||
// go func(storages []model.Storage) {
|
|
||||||
// for i := range storages {
|
|
||||||
// err := s.LoadStorage(context.Background(), storages[i])
|
|
||||||
// if err != nil {
|
|
||||||
// logger.Error("failed get enabled storages", zap.Any("err", err))
|
|
||||||
// } else {
|
|
||||||
// logger.Info("success load storage", zap.String("mount_path", storages[i].MountPath))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// conf.StoragesLoaded = true
|
|
||||||
// }(storages)
|
|
||||||
|
|
||||||
}
|
|
||||||
func NewStoragesService() StoragesService {
|
|
||||||
return &storagesStruct{}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user