mirror of
https://github.com/OpenListTeam/OpenList.git
synced 2025-09-20 12:46:17 +08:00
Compare commits
15 Commits
renovate/g
...
fix-user
Author | SHA1 | Date | |
---|---|---|---|
208e162f60 | |||
048ee9c2e5 | |||
23394548ca | |||
b04677b806 | |||
e4c902dd93 | |||
5d8bd258c0 | |||
08c5283c8c | |||
10a14f10cd | |||
f86ebc52a0 | |||
016ed90efa | |||
d76407b201 | |||
5de6b660f2 | |||
71ada3b656 | |||
dc42f0e226 | |||
74bf9f6467 |
38
.github/workflows/sync_repo.yml
vendored
Normal file
38
.github/workflows/sync_repo.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
name: Sync to Gitee
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Sync GitHub to Gitee
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup SSH
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "${{ secrets.GITEE_SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
|
||||||
|
chmod 600 ~/.ssh/id_rsa
|
||||||
|
ssh-keyscan gitee.com >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
- name: Create single commit and push
|
||||||
|
run: |
|
||||||
|
git config user.name "GitHub Actions"
|
||||||
|
git config user.email "actions@github.com"
|
||||||
|
|
||||||
|
# Create a new branch
|
||||||
|
git checkout --orphan new-main
|
||||||
|
git add .
|
||||||
|
git commit -m "Sync from GitHub: $(date)"
|
||||||
|
|
||||||
|
# Add Gitee remote and force push
|
||||||
|
git remote add gitee ${{ vars.GITEE_REPO_URL }}
|
||||||
|
git push --force gitee new-main:main
|
16
Dockerfile
16
Dockerfile
@ -14,19 +14,17 @@ FROM openlistteam/openlist-base-image:${BASE_IMAGE_TAG}
|
|||||||
LABEL MAINTAINER="OpenList"
|
LABEL MAINTAINER="OpenList"
|
||||||
ARG INSTALL_FFMPEG=false
|
ARG INSTALL_FFMPEG=false
|
||||||
ARG INSTALL_ARIA2=false
|
ARG INSTALL_ARIA2=false
|
||||||
ARG USER=openlist
|
|
||||||
ARG UID=1001
|
|
||||||
ARG GID=1001
|
|
||||||
|
|
||||||
WORKDIR /opt/openlist/
|
WORKDIR /opt/openlist/
|
||||||
|
|
||||||
COPY --chmod=755 --from=builder /app/bin/openlist ./
|
RUN addgroup -g 1001 openlist && \
|
||||||
COPY --chmod=755 entrypoint.sh /entrypoint.sh
|
adduser -D -u 1001 -G openlist openlist && \
|
||||||
RUN adduser -u ${UID} -g ${GID} -h /opt/openlist/data -D -s /bin/sh ${USER} \
|
mkdir -p /opt/openlist/data
|
||||||
&& chown -R ${UID}:${GID} /opt \
|
|
||||||
&& chown -R ${UID}:${GID} /entrypoint.sh
|
|
||||||
|
|
||||||
USER ${USER}
|
COPY --from=builder --chmod=755 --chown=1001:1001 /app/bin/openlist ./
|
||||||
|
COPY --chmod=755 --chown=1001:1001 entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
|
USER openlist
|
||||||
RUN /entrypoint.sh version
|
RUN /entrypoint.sh version
|
||||||
|
|
||||||
ENV UMASK=022 RUN_ARIA2=${INSTALL_ARIA2}
|
ENV UMASK=022 RUN_ARIA2=${INSTALL_ARIA2}
|
||||||
|
@ -4,20 +4,17 @@ LABEL MAINTAINER="OpenList"
|
|||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
ARG INSTALL_FFMPEG=false
|
ARG INSTALL_FFMPEG=false
|
||||||
ARG INSTALL_ARIA2=false
|
ARG INSTALL_ARIA2=false
|
||||||
ARG USER=openlist
|
|
||||||
ARG UID=1001
|
|
||||||
ARG GID=1001
|
|
||||||
|
|
||||||
WORKDIR /opt/openlist/
|
WORKDIR /opt/openlist/
|
||||||
|
|
||||||
COPY --chmod=755 /build/${TARGETPLATFORM}/openlist ./
|
RUN addgroup -g 1001 openlist && \
|
||||||
COPY --chmod=755 entrypoint.sh /entrypoint.sh
|
adduser -D -u 1001 -G openlist openlist && \
|
||||||
|
mkdir -p /opt/openlist/data
|
||||||
|
|
||||||
RUN adduser -u ${UID} -g ${GID} -h /opt/openlist/data -D -s /bin/sh ${USER} \
|
COPY --chmod=755 --chown=1001:1001 /build/${TARGETPLATFORM}/openlist ./
|
||||||
&& chown -R ${UID}:${GID} /opt \
|
COPY --chmod=755 --chown=1001:1001 entrypoint.sh /entrypoint.sh
|
||||||
&& chown -R ${UID}:${GID} /entrypoint.sh
|
|
||||||
|
|
||||||
USER ${USER}
|
USER openlist
|
||||||
RUN /entrypoint.sh version
|
RUN /entrypoint.sh version
|
||||||
|
|
||||||
ENV UMASK=022 RUN_ARIA2=${INSTALL_ARIA2}
|
ENV UMASK=022 RUN_ARIA2=${INSTALL_ARIA2}
|
||||||
|
@ -6,10 +6,9 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- '5244:5244'
|
- '5244:5244'
|
||||||
- '5245:5245'
|
- '5245:5245'
|
||||||
|
user: '0:0'
|
||||||
environment:
|
environment:
|
||||||
- PUID=0
|
|
||||||
- PGID=0
|
|
||||||
- UMASK=022
|
- UMASK=022
|
||||||
- TZ=UTC
|
- TZ=Asia/Shanghai
|
||||||
container_name: openlist
|
container_name: openlist
|
||||||
image: 'openlistteam/openlist:latest'
|
image: 'openlistteam/openlist:latest'
|
||||||
|
@ -69,13 +69,45 @@ func (d *Open123) List(ctx context.Context, dir model.Obj, args model.ListArgs)
|
|||||||
func (d *Open123) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
func (d *Open123) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
fileId, _ := strconv.ParseInt(file.GetID(), 10, 64)
|
fileId, _ := strconv.ParseInt(file.GetID(), 10, 64)
|
||||||
|
|
||||||
|
if d.DirectLink {
|
||||||
|
res, err := d.getDirectLink(fileId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.DirectLinkPrivateKey == "" {
|
||||||
|
duration := 365 * 24 * time.Hour // 缓存1年
|
||||||
|
return &model.Link{
|
||||||
|
URL: res.Data.URL,
|
||||||
|
Expiration: &duration,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := d.getUserInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
duration := time.Duration(d.DirectLinkValidDuration) * time.Minute
|
||||||
|
|
||||||
|
newURL, err := d.SignURL(res.Data.URL, d.DirectLinkPrivateKey,
|
||||||
|
u.Data.UID, duration)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Link{
|
||||||
|
URL: newURL,
|
||||||
|
Expiration: &duration,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
res, err := d.getDownloadInfo(fileId)
|
res, err := d.getDownloadInfo(fileId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
link := model.Link{URL: res.Data.DownloadUrl}
|
return &model.Link{URL: res.Data.DownloadUrl}, nil
|
||||||
return &link, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Open123) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
func (d *Open123) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||||
|
@ -23,6 +23,11 @@ type Addition struct {
|
|||||||
// 上传线程数
|
// 上传线程数
|
||||||
UploadThread int `json:"UploadThread" type:"number" default:"3" help:"the threads of upload"`
|
UploadThread int `json:"UploadThread" type:"number" default:"3" help:"the threads of upload"`
|
||||||
|
|
||||||
|
// 使用直链
|
||||||
|
DirectLink bool `json:"DirectLink" type:"boolean" default:"false" required:"false" help:"use direct link when download file"`
|
||||||
|
DirectLinkPrivateKey string `json:"DirectLinkPrivateKey" required:"false" help:"private key for direct link, if URL authentication is enabled"`
|
||||||
|
DirectLinkValidDuration int64 `json:"DirectLinkValidDuration" type:"number" default:"30" required:"false" help:"minutes, if URL authentication is enabled"`
|
||||||
|
|
||||||
driver.RootID
|
driver.RootID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ type RefreshTokenResp struct {
|
|||||||
type UserInfoResp struct {
|
type UserInfoResp struct {
|
||||||
BaseResp
|
BaseResp
|
||||||
Data struct {
|
Data struct {
|
||||||
UID int64 `json:"uid"`
|
UID uint64 `json:"uid"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
DisplayName string `json:"displayName"`
|
DisplayName string `json:"displayName"`
|
||||||
HeadImage string `json:"headImage"`
|
HeadImage string `json:"headImage"`
|
||||||
@ -158,6 +158,13 @@ type DownloadInfoResp struct {
|
|||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DirectLinkResp struct {
|
||||||
|
BaseResp
|
||||||
|
Data struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
// 创建文件V2返回
|
// 创建文件V2返回
|
||||||
type UploadCreateResp struct {
|
type UploadCreateResp struct {
|
||||||
BaseResp
|
BaseResp
|
||||||
|
@ -70,6 +70,8 @@ func (d *Open123) Upload(ctx context.Context, file model.FileStreamer, createRes
|
|||||||
var reader *stream.SectionReader
|
var reader *stream.SectionReader
|
||||||
var rateLimitedRd io.Reader
|
var rateLimitedRd io.Reader
|
||||||
sliceMD5 := ""
|
sliceMD5 := ""
|
||||||
|
// 表单
|
||||||
|
b := bytes.NewBuffer(make([]byte, 0, 2048))
|
||||||
threadG.GoWithLifecycle(errgroup.Lifecycle{
|
threadG.GoWithLifecycle(errgroup.Lifecycle{
|
||||||
Before: func(ctx context.Context) error {
|
Before: func(ctx context.Context) error {
|
||||||
if reader == nil {
|
if reader == nil {
|
||||||
@ -84,7 +86,6 @@ func (d *Open123) Upload(ctx context.Context, file model.FileStreamer, createRes
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rateLimitedRd = driver.NewLimitedUploadStream(ctx, reader)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@ -92,9 +93,8 @@ func (d *Open123) Upload(ctx context.Context, file model.FileStreamer, createRes
|
|||||||
// 重置分片reader位置,因为HashReader、上一次失败已经读取到分片EOF
|
// 重置分片reader位置,因为HashReader、上一次失败已经读取到分片EOF
|
||||||
reader.Seek(0, io.SeekStart)
|
reader.Seek(0, io.SeekStart)
|
||||||
|
|
||||||
// 创建表单数据
|
b.Reset()
|
||||||
var b bytes.Buffer
|
w := multipart.NewWriter(b)
|
||||||
w := multipart.NewWriter(&b)
|
|
||||||
// 添加表单字段
|
// 添加表单字段
|
||||||
err = w.WriteField("preuploadID", createResp.Data.PreuploadID)
|
err = w.WriteField("preuploadID", createResp.Data.PreuploadID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -109,21 +109,20 @@ func (d *Open123) Upload(ctx context.Context, file model.FileStreamer, createRes
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// 写入文件内容
|
// 写入文件内容
|
||||||
fw, err := w.CreateFormFile("slice", fmt.Sprintf("%s.part%d", file.GetName(), partNumber))
|
_, err = w.CreateFormFile("slice", fmt.Sprintf("%s.part%d", file.GetName(), partNumber))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = utils.CopyWithBuffer(fw, rateLimitedRd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
headSize := b.Len()
|
||||||
err = w.Close()
|
err = w.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
head := bytes.NewReader(b.Bytes()[:headSize])
|
||||||
|
tail := bytes.NewReader(b.Bytes()[headSize:])
|
||||||
|
rateLimitedRd = driver.NewLimitedUploadStream(ctx, io.MultiReader(head, reader, tail))
|
||||||
// 创建请求并设置header
|
// 创建请求并设置header
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, uploadDomain+"/upload/v2/file/slice", &b)
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, uploadDomain+"/upload/v2/file/slice", rateLimitedRd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
package _123_open
|
package _123_open
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/md5"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/OpenListTeam/OpenList/v4/drivers/base"
|
"github.com/OpenListTeam/OpenList/v4/drivers/base"
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
|
"github.com/google/uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,7 +25,8 @@ var ( //不同情况下获取的AccessTokenQPS限制不同 如下模块化易于
|
|||||||
RefreshToken = InitApiInfo(Api+"/api/v1/oauth2/access_token", 1)
|
RefreshToken = InitApiInfo(Api+"/api/v1/oauth2/access_token", 1)
|
||||||
UserInfo = InitApiInfo(Api+"/api/v1/user/info", 1)
|
UserInfo = InitApiInfo(Api+"/api/v1/user/info", 1)
|
||||||
FileList = InitApiInfo(Api+"/api/v2/file/list", 3)
|
FileList = InitApiInfo(Api+"/api/v2/file/list", 3)
|
||||||
DownloadInfo = InitApiInfo(Api+"/api/v1/file/download_info", 0)
|
DownloadInfo = InitApiInfo(Api+"/api/v1/file/download_info", 5)
|
||||||
|
DirectLink = InitApiInfo(Api+"/api/v1/direct-link/url", 5)
|
||||||
Mkdir = InitApiInfo(Api+"/upload/v1/file/mkdir", 2)
|
Mkdir = InitApiInfo(Api+"/upload/v1/file/mkdir", 2)
|
||||||
Move = InitApiInfo(Api+"/api/v1/file/move", 1)
|
Move = InitApiInfo(Api+"/api/v1/file/move", 1)
|
||||||
Rename = InitApiInfo(Api+"/api/v1/file/name", 1)
|
Rename = InitApiInfo(Api+"/api/v1/file/name", 1)
|
||||||
@ -112,6 +118,33 @@ func (d *Open123) flushAccessToken() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Open123) SignURL(originURL, privateKey string, uid uint64, validDuration time.Duration) (newURL string, err error) {
|
||||||
|
// 生成Unix时间戳
|
||||||
|
ts := time.Now().Add(validDuration).Unix()
|
||||||
|
|
||||||
|
// 生成随机数(建议使用UUID,不能包含中划线(-))
|
||||||
|
rand := strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||||
|
|
||||||
|
// 解析URL
|
||||||
|
objURL, err := url.Parse(originURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 待签名字符串,格式:path-timestamp-rand-uid-privateKey
|
||||||
|
unsignedStr := fmt.Sprintf("%s-%d-%s-%d-%s", objURL.Path, ts, rand, uid, privateKey)
|
||||||
|
md5Hash := md5.Sum([]byte(unsignedStr))
|
||||||
|
// 生成鉴权参数,格式:timestamp-rand-uid-md5hash
|
||||||
|
authKey := fmt.Sprintf("%d-%s-%d-%x", ts, rand, uid, md5Hash)
|
||||||
|
|
||||||
|
// 添加鉴权参数到URL查询参数
|
||||||
|
v := objURL.Query()
|
||||||
|
v.Add("auth_key", authKey)
|
||||||
|
objURL.RawQuery = v.Encode()
|
||||||
|
|
||||||
|
return objURL.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Open123) getUserInfo() (*UserInfoResp, error) {
|
func (d *Open123) getUserInfo() (*UserInfoResp, error) {
|
||||||
var resp UserInfoResp
|
var resp UserInfoResp
|
||||||
|
|
||||||
@ -159,6 +192,21 @@ func (d *Open123) getDownloadInfo(fileId int64) (*DownloadInfoResp, error) {
|
|||||||
return &resp, nil
|
return &resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Open123) getDirectLink(fileId int64) (*DirectLinkResp, error) {
|
||||||
|
var resp DirectLinkResp
|
||||||
|
|
||||||
|
_, err := d.Request(DirectLink, http.MethodGet, func(req *resty.Request) {
|
||||||
|
req.SetQueryParams(map[string]string{
|
||||||
|
"fileId": strconv.FormatInt(fileId, 10),
|
||||||
|
})
|
||||||
|
}, &resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Open123) mkdir(parentID int64, name string) error {
|
func (d *Open123) mkdir(parentID int64, name string) error {
|
||||||
_, err := d.Request(Mkdir, http.MethodPost, func(req *resty.Request) {
|
_, err := d.Request(Mkdir, http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(base.Json{
|
req.SetBody(base.Json{
|
||||||
|
@ -48,6 +48,7 @@ import (
|
|||||||
_ "github.com/OpenListTeam/OpenList/v4/drivers/onedrive_app"
|
_ "github.com/OpenListTeam/OpenList/v4/drivers/onedrive_app"
|
||||||
_ "github.com/OpenListTeam/OpenList/v4/drivers/onedrive_sharelink"
|
_ "github.com/OpenListTeam/OpenList/v4/drivers/onedrive_sharelink"
|
||||||
_ "github.com/OpenListTeam/OpenList/v4/drivers/openlist"
|
_ "github.com/OpenListTeam/OpenList/v4/drivers/openlist"
|
||||||
|
_ "github.com/OpenListTeam/OpenList/v4/drivers/openlist_share"
|
||||||
_ "github.com/OpenListTeam/OpenList/v4/drivers/pikpak"
|
_ "github.com/OpenListTeam/OpenList/v4/drivers/pikpak"
|
||||||
_ "github.com/OpenListTeam/OpenList/v4/drivers/pikpak_share"
|
_ "github.com/OpenListTeam/OpenList/v4/drivers/pikpak_share"
|
||||||
_ "github.com/OpenListTeam/OpenList/v4/drivers/quark_open"
|
_ "github.com/OpenListTeam/OpenList/v4/drivers/quark_open"
|
||||||
|
@ -13,7 +13,7 @@ type Addition struct {
|
|||||||
ClientSecret string `json:"client_secret" required:"false" help:"Keep it empty if you don't have one"`
|
ClientSecret string `json:"client_secret" required:"false" help:"Keep it empty if you don't have one"`
|
||||||
AccessToken string
|
AccessToken string
|
||||||
RefreshToken string `json:"refresh_token" required:"true"`
|
RefreshToken string `json:"refresh_token" required:"true"`
|
||||||
RootNamespaceId string
|
RootNamespaceId string `json:"RootNamespaceId" required:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
|
@ -175,6 +175,13 @@ func (d *Dropbox) finishUploadSession(ctx context.Context, toPath string, offset
|
|||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/octet-stream")
|
req.Header.Set("Content-Type", "application/octet-stream")
|
||||||
req.Header.Set("Authorization", "Bearer "+d.AccessToken)
|
req.Header.Set("Authorization", "Bearer "+d.AccessToken)
|
||||||
|
if d.RootNamespaceId != "" {
|
||||||
|
apiPathRootJson, err := d.buildPathRootHeader()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Set("Dropbox-API-Path-Root", apiPathRootJson)
|
||||||
|
}
|
||||||
|
|
||||||
uploadFinishArgs := UploadFinishArgs{
|
uploadFinishArgs := UploadFinishArgs{
|
||||||
Commit: struct {
|
Commit: struct {
|
||||||
@ -219,6 +226,13 @@ func (d *Dropbox) startUploadSession(ctx context.Context) (string, error) {
|
|||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/octet-stream")
|
req.Header.Set("Content-Type", "application/octet-stream")
|
||||||
req.Header.Set("Authorization", "Bearer "+d.AccessToken)
|
req.Header.Set("Authorization", "Bearer "+d.AccessToken)
|
||||||
|
if d.RootNamespaceId != "" {
|
||||||
|
apiPathRootJson, err := d.buildPathRootHeader()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
req.Header.Set("Dropbox-API-Path-Root", apiPathRootJson)
|
||||||
|
}
|
||||||
req.Header.Set("Dropbox-API-Arg", "{\"close\":false}")
|
req.Header.Set("Dropbox-API-Arg", "{\"close\":false}")
|
||||||
|
|
||||||
res, err := base.HttpClient.Do(req)
|
res, err := base.HttpClient.Do(req)
|
||||||
@ -233,3 +247,11 @@ func (d *Dropbox) startUploadSession(ctx context.Context) (string, error) {
|
|||||||
_ = res.Body.Close()
|
_ = res.Body.Close()
|
||||||
return sessionId, nil
|
return sessionId, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Dropbox) buildPathRootHeader() (string, error) {
|
||||||
|
return utils.Json.MarshalToString(map[string]interface{}{
|
||||||
|
".tag": "root",
|
||||||
|
"root": d.RootNamespaceId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
181
drivers/openlist_share/driver.go
Normal file
181
drivers/openlist_share/driver.go
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
package openlist_share
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
stdpath "path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/server/common"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OpenListShare struct {
|
||||||
|
model.Storage
|
||||||
|
Addition
|
||||||
|
serverArchivePreview bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OpenListShare) Config() driver.Config {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OpenListShare) GetAddition() driver.Additional {
|
||||||
|
return &d.Addition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OpenListShare) Init(ctx context.Context) error {
|
||||||
|
d.Addition.Address = strings.TrimSuffix(d.Addition.Address, "/")
|
||||||
|
var settings common.Resp[map[string]string]
|
||||||
|
_, _, err := d.request("/public/settings", http.MethodGet, func(req *resty.Request) {
|
||||||
|
req.SetResult(&settings)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.serverArchivePreview = settings.Data["share_archive_preview"] == "true"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OpenListShare) Drop(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OpenListShare) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
|
var resp common.Resp[FsListResp]
|
||||||
|
_, _, err := d.request("/fs/list", http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetResult(&resp).SetBody(ListReq{
|
||||||
|
PageReq: model.PageReq{
|
||||||
|
Page: 1,
|
||||||
|
PerPage: 0,
|
||||||
|
},
|
||||||
|
Path: stdpath.Join(fmt.Sprintf("/@s/%s", d.ShareId), dir.GetPath()),
|
||||||
|
Password: d.Pwd,
|
||||||
|
Refresh: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var files []model.Obj
|
||||||
|
for _, f := range resp.Data.Content {
|
||||||
|
file := model.ObjThumb{
|
||||||
|
Object: model.Object{
|
||||||
|
Name: f.Name,
|
||||||
|
Modified: f.Modified,
|
||||||
|
Ctime: f.Created,
|
||||||
|
Size: f.Size,
|
||||||
|
IsFolder: f.IsDir,
|
||||||
|
HashInfo: utils.FromString(f.HashInfo),
|
||||||
|
},
|
||||||
|
Thumbnail: model.Thumbnail{Thumbnail: f.Thumb},
|
||||||
|
}
|
||||||
|
files = append(files, &file)
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OpenListShare) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
|
path := utils.FixAndCleanPath(stdpath.Join(d.ShareId, file.GetPath()))
|
||||||
|
u := fmt.Sprintf("%s/sd%s?pwd=%s", d.Address, path, d.Pwd)
|
||||||
|
return &model.Link{URL: u}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OpenListShare) GetArchiveMeta(ctx context.Context, obj model.Obj, args model.ArchiveArgs) (model.ArchiveMeta, error) {
|
||||||
|
if !d.serverArchivePreview || !d.ForwardArchiveReq {
|
||||||
|
return nil, errs.NotImplement
|
||||||
|
}
|
||||||
|
var resp common.Resp[ArchiveMetaResp]
|
||||||
|
_, code, err := d.request("/fs/archive/meta", http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetResult(&resp).SetBody(ArchiveMetaReq{
|
||||||
|
ArchivePass: args.Password,
|
||||||
|
Path: stdpath.Join(fmt.Sprintf("/@s/%s", d.ShareId), obj.GetPath()),
|
||||||
|
Password: d.Pwd,
|
||||||
|
Refresh: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if code == 202 {
|
||||||
|
return nil, errs.WrongArchivePassword
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var tree []model.ObjTree
|
||||||
|
if resp.Data.Content != nil {
|
||||||
|
tree = make([]model.ObjTree, 0, len(resp.Data.Content))
|
||||||
|
for _, content := range resp.Data.Content {
|
||||||
|
tree = append(tree, &content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &model.ArchiveMetaInfo{
|
||||||
|
Comment: resp.Data.Comment,
|
||||||
|
Encrypted: resp.Data.Encrypted,
|
||||||
|
Tree: tree,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OpenListShare) ListArchive(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) ([]model.Obj, error) {
|
||||||
|
if !d.serverArchivePreview || !d.ForwardArchiveReq {
|
||||||
|
return nil, errs.NotImplement
|
||||||
|
}
|
||||||
|
var resp common.Resp[ArchiveListResp]
|
||||||
|
_, code, err := d.request("/fs/archive/list", http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetResult(&resp).SetBody(ArchiveListReq{
|
||||||
|
ArchiveMetaReq: ArchiveMetaReq{
|
||||||
|
ArchivePass: args.Password,
|
||||||
|
Path: stdpath.Join(fmt.Sprintf("/@s/%s", d.ShareId), obj.GetPath()),
|
||||||
|
Password: d.Pwd,
|
||||||
|
Refresh: false,
|
||||||
|
},
|
||||||
|
PageReq: model.PageReq{
|
||||||
|
Page: 1,
|
||||||
|
PerPage: 0,
|
||||||
|
},
|
||||||
|
InnerPath: args.InnerPath,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if code == 202 {
|
||||||
|
return nil, errs.WrongArchivePassword
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var files []model.Obj
|
||||||
|
for _, f := range resp.Data.Content {
|
||||||
|
file := model.ObjThumb{
|
||||||
|
Object: model.Object{
|
||||||
|
Name: f.Name,
|
||||||
|
Modified: f.Modified,
|
||||||
|
Ctime: f.Created,
|
||||||
|
Size: f.Size,
|
||||||
|
IsFolder: f.IsDir,
|
||||||
|
HashInfo: utils.FromString(f.HashInfo),
|
||||||
|
},
|
||||||
|
Thumbnail: model.Thumbnail{Thumbnail: f.Thumb},
|
||||||
|
}
|
||||||
|
files = append(files, &file)
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OpenListShare) Extract(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) (*model.Link, error) {
|
||||||
|
if !d.serverArchivePreview || !d.ForwardArchiveReq {
|
||||||
|
return nil, errs.NotSupport
|
||||||
|
}
|
||||||
|
path := utils.FixAndCleanPath(stdpath.Join(d.ShareId, obj.GetPath()))
|
||||||
|
u := fmt.Sprintf("%s/sad%s?pwd=%s&inner=%s&pass=%s",
|
||||||
|
d.Address,
|
||||||
|
path,
|
||||||
|
d.Pwd,
|
||||||
|
utils.EncodePath(args.InnerPath, true),
|
||||||
|
url.QueryEscape(args.Password))
|
||||||
|
return &model.Link{URL: u}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ driver.Driver = (*OpenListShare)(nil)
|
27
drivers/openlist_share/meta.go
Normal file
27
drivers/openlist_share/meta.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package openlist_share
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Addition struct {
|
||||||
|
driver.RootPath
|
||||||
|
Address string `json:"url" required:"true"`
|
||||||
|
ShareId string `json:"sid" required:"true"`
|
||||||
|
Pwd string `json:"pwd"`
|
||||||
|
ForwardArchiveReq bool `json:"forward_archive_requests" default:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = driver.Config{
|
||||||
|
Name: "OpenListShare",
|
||||||
|
LocalSort: true,
|
||||||
|
NoUpload: true,
|
||||||
|
DefaultRoot: "/",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
op.RegisterDriver(func() driver.Driver {
|
||||||
|
return &OpenListShare{}
|
||||||
|
})
|
||||||
|
}
|
111
drivers/openlist_share/types.go
Normal file
111
drivers/openlist_share/types.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package openlist_share
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListReq struct {
|
||||||
|
model.PageReq
|
||||||
|
Path string `json:"path" form:"path"`
|
||||||
|
Password string `json:"password" form:"password"`
|
||||||
|
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"`
|
||||||
|
Created time.Time `json:"created"`
|
||||||
|
Sign string `json:"sign"`
|
||||||
|
Thumb string `json:"thumb"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
HashInfo string `json:"hashinfo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FsListResp struct {
|
||||||
|
Content []ObjResp `json:"content"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Readme string `json:"readme"`
|
||||||
|
Write bool `json:"write"`
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArchiveMetaReq struct {
|
||||||
|
ArchivePass string `json:"archive_pass"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Refresh bool `json:"refresh"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TreeResp struct {
|
||||||
|
ObjResp
|
||||||
|
Children []TreeResp `json:"children"`
|
||||||
|
hashCache *utils.HashInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TreeResp) GetSize() int64 {
|
||||||
|
return t.Size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TreeResp) GetName() string {
|
||||||
|
return t.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TreeResp) ModTime() time.Time {
|
||||||
|
return t.Modified
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TreeResp) CreateTime() time.Time {
|
||||||
|
return t.Created
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TreeResp) IsDir() bool {
|
||||||
|
return t.ObjResp.IsDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TreeResp) GetHash() utils.HashInfo {
|
||||||
|
return utils.FromString(t.HashInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TreeResp) GetID() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TreeResp) GetPath() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TreeResp) GetChildren() []model.ObjTree {
|
||||||
|
ret := make([]model.ObjTree, 0, len(t.Children))
|
||||||
|
for _, child := range t.Children {
|
||||||
|
ret = append(ret, &child)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TreeResp) Thumb() string {
|
||||||
|
return t.ObjResp.Thumb
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArchiveMetaResp struct {
|
||||||
|
Comment string `json:"comment"`
|
||||||
|
Encrypted bool `json:"encrypted"`
|
||||||
|
Content []TreeResp `json:"content"`
|
||||||
|
RawURL string `json:"raw_url"`
|
||||||
|
Sign string `json:"sign"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArchiveListReq struct {
|
||||||
|
model.PageReq
|
||||||
|
ArchiveMetaReq
|
||||||
|
InnerPath string `json:"inner_path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArchiveListResp struct {
|
||||||
|
Content []ObjResp `json:"content"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
}
|
32
drivers/openlist_share/util.go
Normal file
32
drivers/openlist_share/util.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package openlist_share
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/drivers/base"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *OpenListShare) request(api, method string, callback base.ReqCallback) ([]byte, int, error) {
|
||||||
|
url := d.Address + "/api" + api
|
||||||
|
req := base.RestyClient.R()
|
||||||
|
if callback != nil {
|
||||||
|
callback(req)
|
||||||
|
}
|
||||||
|
res, err := req.Execute(method, url)
|
||||||
|
if err != nil {
|
||||||
|
code := 0
|
||||||
|
if res != nil {
|
||||||
|
code = res.StatusCode()
|
||||||
|
}
|
||||||
|
return nil, code, err
|
||||||
|
}
|
||||||
|
if res.StatusCode() >= 400 {
|
||||||
|
return nil, res.StatusCode(), fmt.Errorf("request failed, status: %s", res.Status())
|
||||||
|
}
|
||||||
|
code := utils.Json.Get(res.Body(), "code").ToInt()
|
||||||
|
if code != 200 {
|
||||||
|
return nil, code, fmt.Errorf("request failed, code: %d, message: %s", code, utils.Json.Get(res.Body(), "message").ToString())
|
||||||
|
}
|
||||||
|
return res.Body(), 200, nil
|
||||||
|
}
|
@ -132,7 +132,7 @@ func (d *Terabox) Remove(ctx context.Context, obj model.Obj) error {
|
|||||||
func (d *Terabox) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
func (d *Terabox) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
resp, err := base.RestyClient.R().
|
resp, err := base.RestyClient.R().
|
||||||
SetContext(ctx).
|
SetContext(ctx).
|
||||||
Get("https://d.terabox.com/rest/2.0/pcs/file?method=locateupload")
|
Get("https://" + d.url_domain_prefix + "-data.terabox.com/rest/2.0/pcs/file?method=locateupload")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,6 @@ umask ${UMASK}
|
|||||||
if [ "$1" = "version" ]; then
|
if [ "$1" = "version" ]; then
|
||||||
./openlist version
|
./openlist version
|
||||||
else
|
else
|
||||||
# Define the target directory path for openlist service
|
|
||||||
OPENLIST_DIR="/opt/service/start/openlist"
|
|
||||||
if [ ! -d "$OPENLIST_DIR" ]; then
|
|
||||||
cp -r /opt/service/stop/openlist "$OPENLIST_DIR" 2>/dev/null
|
|
||||||
fi
|
|
||||||
# Define the target directory path for aria2 service
|
# Define the target directory path for aria2 service
|
||||||
ARIA2_DIR="/opt/service/start/aria2"
|
ARIA2_DIR="/opt/service/start/aria2"
|
||||||
|
|
||||||
@ -19,12 +14,12 @@ else
|
|||||||
mkdir -p "$ARIA2_DIR"
|
mkdir -p "$ARIA2_DIR"
|
||||||
cp -r /opt/service/stop/aria2/* "$ARIA2_DIR" 2>/dev/null
|
cp -r /opt/service/stop/aria2/* "$ARIA2_DIR" 2>/dev/null
|
||||||
fi
|
fi
|
||||||
|
runsvdir /opt/service/start &
|
||||||
else
|
else
|
||||||
# If aria2 should NOT run and target directory exists, remove it
|
# If aria2 should NOT run and target directory exists, remove it
|
||||||
if [ -d "$ARIA2_DIR" ]; then
|
if [ -d "$ARIA2_DIR" ]; then
|
||||||
rm -rf "$ARIA2_DIR"
|
rm -rf "$ARIA2_DIR"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
exec ./openlist server --no-prefix
|
||||||
exec runsvdir /opt/service/start
|
|
||||||
fi
|
fi
|
||||||
|
8
go.mod
8
go.mod
@ -73,7 +73,7 @@ require (
|
|||||||
google.golang.org/appengine v1.6.8
|
google.golang.org/appengine v1.6.8
|
||||||
gopkg.in/ldap.v3 v3.1.0
|
gopkg.in/ldap.v3 v3.1.0
|
||||||
gorm.io/driver/mysql v1.5.7
|
gorm.io/driver/mysql v1.5.7
|
||||||
gorm.io/driver/postgres v1.6.0
|
gorm.io/driver/postgres v1.5.9
|
||||||
gorm.io/driver/sqlite v1.5.6
|
gorm.io/driver/sqlite v1.5.6
|
||||||
gorm.io/gorm v1.25.11
|
gorm.io/gorm v1.25.11
|
||||||
)
|
)
|
||||||
@ -118,7 +118,7 @@ require (
|
|||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
github.com/hekmon/cunits/v2 v2.1.0 // indirect
|
github.com/hekmon/cunits/v2 v2.1.0 // indirect
|
||||||
github.com/ipfs/boxo v0.12.0 // indirect
|
github.com/ipfs/boxo v0.12.0 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||||
github.com/matoous/go-nanoid/v2 v2.1.0 // indirect
|
github.com/matoous/go-nanoid/v2 v2.1.0 // indirect
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27
|
github.com/microcosm-cc/bluemonday v1.0.27
|
||||||
@ -188,8 +188,8 @@ require (
|
|||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/ipfs/go-cid v0.5.0
|
github.com/ipfs/go-cid v0.5.0
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
github.com/jackc/pgx/v5 v5.5.5 // 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/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
|
8
go.sum
8
go.sum
@ -392,16 +392,10 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
|||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
|
||||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||||
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
|
||||||
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
@ -954,8 +948,6 @@ gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
|||||||
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||||
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
|
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
|
||||||
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
|
||||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
|
||||||
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
|
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
|
||||||
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
|
@ -77,6 +77,10 @@ func InitConfig() {
|
|||||||
log.Fatalf("update config struct error: %+v", err)
|
log.Fatalf("update config struct error: %+v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !conf.Conf.Force {
|
||||||
|
confFromEnv()
|
||||||
|
}
|
||||||
|
|
||||||
if conf.Conf.MaxConcurrency > 0 {
|
if conf.Conf.MaxConcurrency > 0 {
|
||||||
net.DefaultConcurrencyLimit = &net.ConcurrencyLimit{Limit: conf.Conf.MaxConcurrency}
|
net.DefaultConcurrencyLimit = &net.ConcurrencyLimit{Limit: conf.Conf.MaxConcurrency}
|
||||||
}
|
}
|
||||||
@ -92,25 +96,31 @@ func InitConfig() {
|
|||||||
conf.MaxBufferLimit = conf.Conf.MaxBufferLimit * utils.MB
|
conf.MaxBufferLimit = conf.Conf.MaxBufferLimit * utils.MB
|
||||||
}
|
}
|
||||||
log.Infof("max buffer limit: %dMB", conf.MaxBufferLimit/utils.MB)
|
log.Infof("max buffer limit: %dMB", conf.MaxBufferLimit/utils.MB)
|
||||||
if !conf.Conf.Force {
|
if conf.Conf.MmapThreshold > 0 {
|
||||||
confFromEnv()
|
conf.MmapThreshold = conf.Conf.MmapThreshold * utils.MB
|
||||||
|
} else {
|
||||||
|
conf.MmapThreshold = 0
|
||||||
}
|
}
|
||||||
|
log.Infof("mmap threshold: %dMB", conf.Conf.MmapThreshold)
|
||||||
|
|
||||||
if len(conf.Conf.Log.Filter.Filters) == 0 {
|
if len(conf.Conf.Log.Filter.Filters) == 0 {
|
||||||
conf.Conf.Log.Filter.Enable = false
|
conf.Conf.Log.Filter.Enable = false
|
||||||
}
|
}
|
||||||
// convert abs path
|
// convert abs path
|
||||||
convertAbsPath := func(path *string) {
|
convertAbsPath := func(path *string) {
|
||||||
if !filepath.IsAbs(*path) {
|
if *path != "" && !filepath.IsAbs(*path) {
|
||||||
*path = filepath.Join(pwd, *path)
|
*path = filepath.Join(pwd, *path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
convertAbsPath(&conf.Conf.Database.DBFile)
|
||||||
|
convertAbsPath(&conf.Conf.Scheme.CertFile)
|
||||||
|
convertAbsPath(&conf.Conf.Scheme.KeyFile)
|
||||||
|
convertAbsPath(&conf.Conf.Scheme.UnixFile)
|
||||||
|
convertAbsPath(&conf.Conf.Log.Name)
|
||||||
convertAbsPath(&conf.Conf.TempDir)
|
convertAbsPath(&conf.Conf.TempDir)
|
||||||
convertAbsPath(&conf.Conf.BleveDir)
|
convertAbsPath(&conf.Conf.BleveDir)
|
||||||
convertAbsPath(&conf.Conf.Log.Name)
|
|
||||||
convertAbsPath(&conf.Conf.Database.DBFile)
|
|
||||||
if conf.Conf.DistDir != "" {
|
|
||||||
convertAbsPath(&conf.Conf.DistDir)
|
convertAbsPath(&conf.Conf.DistDir)
|
||||||
}
|
|
||||||
err := os.MkdirAll(conf.Conf.TempDir, 0o777)
|
err := os.MkdirAll(conf.Conf.TempDir, 0o777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("create temp dir error: %+v", err)
|
log.Fatalf("create temp dir error: %+v", err)
|
||||||
|
@ -111,6 +111,7 @@ func InitialSettings() []model.SettingItem {
|
|||||||
{Key: conf.Favicon, Value: "https://res.oplist.org/logo/logo.svg", MigrationValue: "https://cdn.oplist.org/gh/OpenListTeam/Logo@main/logo.svg", Type: conf.TypeString, Group: model.STYLE},
|
{Key: conf.Favicon, Value: "https://res.oplist.org/logo/logo.svg", MigrationValue: "https://cdn.oplist.org/gh/OpenListTeam/Logo@main/logo.svg", Type: conf.TypeString, Group: model.STYLE},
|
||||||
{Key: conf.MainColor, Value: "#1890ff", Type: conf.TypeString, Group: model.STYLE},
|
{Key: conf.MainColor, Value: "#1890ff", Type: conf.TypeString, Group: model.STYLE},
|
||||||
{Key: "home_icon", Value: "🏠", Type: conf.TypeString, Group: model.STYLE},
|
{Key: "home_icon", Value: "🏠", Type: conf.TypeString, Group: model.STYLE},
|
||||||
|
{Key: "share_icon", Value: "🎁", Type: conf.TypeString, Group: model.STYLE},
|
||||||
{Key: "home_container", Value: "max_980px", Type: conf.TypeSelect, Options: "max_980px,hope_container", Group: model.STYLE},
|
{Key: "home_container", Value: "max_980px", Type: conf.TypeSelect, Options: "max_980px,hope_container", Group: model.STYLE},
|
||||||
{Key: "settings_layout", Value: "list", Type: conf.TypeSelect, Options: "list,responsive", Group: model.STYLE},
|
{Key: "settings_layout", Value: "list", Type: conf.TypeSelect, Options: "list,responsive", Group: model.STYLE},
|
||||||
// preview settings
|
// preview settings
|
||||||
@ -163,6 +164,10 @@ func InitialSettings() []model.SettingItem {
|
|||||||
{Key: conf.ForwardDirectLinkParams, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL},
|
{Key: conf.ForwardDirectLinkParams, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL},
|
||||||
{Key: conf.IgnoreDirectLinkParams, Value: "sign,openlist_ts", Type: conf.TypeString, Group: model.GLOBAL},
|
{Key: conf.IgnoreDirectLinkParams, Value: "sign,openlist_ts", Type: conf.TypeString, Group: model.GLOBAL},
|
||||||
{Key: conf.WebauthnLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PUBLIC},
|
{Key: conf.WebauthnLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PUBLIC},
|
||||||
|
{Key: conf.SharePreview, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PUBLIC},
|
||||||
|
{Key: conf.ShareArchivePreview, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PUBLIC},
|
||||||
|
{Key: conf.ShareForceProxy, Value: "true", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||||
|
{Key: conf.ShareSummaryContent, Value: "@{{creator}} shared {{#each files}}{{#if @first}}\"{{filename this}}\"{{/if}}{{#if @last}}{{#unless (eq @index 0)}} and {{@index}} more files{{/unless}}{{/if}}{{/each}} from {{site_title}}: {{base_url}}/@s/{{id}}{{#if pwd}} , the share code is {{pwd}}{{/if}}{{#if expires}}, please access before {{dateLocaleString expires}}.{{/if}}", Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PUBLIC},
|
||||||
|
|
||||||
// single settings
|
// single settings
|
||||||
{Key: conf.Token, Value: token, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
{Key: conf.Token, Value: token, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||||
|
@ -33,8 +33,8 @@ func initUser() {
|
|||||||
Role: model.ADMIN,
|
Role: model.ADMIN,
|
||||||
BasePath: "/",
|
BasePath: "/",
|
||||||
Authn: "[]",
|
Authn: "[]",
|
||||||
// 0(can see hidden) - 7(can remove) & 12(can read archives) - 13(can decompress archives)
|
// 0(can see hidden) - 8(webdav read) & 12(can read archives) - 14(can share)
|
||||||
Permission: 0x31FF,
|
Permission: 0x71FF,
|
||||||
}
|
}
|
||||||
if err := op.CreateUser(admin); err != nil {
|
if err := op.CreateUser(admin); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -120,6 +120,7 @@ type Config struct {
|
|||||||
Log LogConfig `json:"log" envPrefix:"LOG_"`
|
Log LogConfig `json:"log" envPrefix:"LOG_"`
|
||||||
DelayedStart int `json:"delayed_start" env:"DELAYED_START"`
|
DelayedStart int `json:"delayed_start" env:"DELAYED_START"`
|
||||||
MaxBufferLimit int `json:"max_buffer_limitMB" env:"MAX_BUFFER_LIMIT_MB"`
|
MaxBufferLimit int `json:"max_buffer_limitMB" env:"MAX_BUFFER_LIMIT_MB"`
|
||||||
|
MmapThreshold int `json:"mmap_thresholdMB" env:"MMAP_THRESHOLD_MB"`
|
||||||
MaxConnections int `json:"max_connections" env:"MAX_CONNECTIONS"`
|
MaxConnections int `json:"max_connections" env:"MAX_CONNECTIONS"`
|
||||||
MaxConcurrency int `json:"max_concurrency" env:"MAX_CONCURRENCY"`
|
MaxConcurrency int `json:"max_concurrency" env:"MAX_CONCURRENCY"`
|
||||||
TlsInsecureSkipVerify bool `json:"tls_insecure_skip_verify" env:"TLS_INSECURE_SKIP_VERIFY"`
|
TlsInsecureSkipVerify bool `json:"tls_insecure_skip_verify" env:"TLS_INSECURE_SKIP_VERIFY"`
|
||||||
@ -176,6 +177,7 @@ func DefaultConfig(dataDir string) *Config {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
MaxBufferLimit: -1,
|
MaxBufferLimit: -1,
|
||||||
|
MmapThreshold: 4,
|
||||||
MaxConnections: 0,
|
MaxConnections: 0,
|
||||||
MaxConcurrency: 64,
|
MaxConcurrency: 64,
|
||||||
TlsInsecureSkipVerify: true,
|
TlsInsecureSkipVerify: true,
|
||||||
|
@ -33,6 +33,7 @@ const (
|
|||||||
PreviewArchivesByDefault = "preview_archives_by_default"
|
PreviewArchivesByDefault = "preview_archives_by_default"
|
||||||
ReadMeAutoRender = "readme_autorender"
|
ReadMeAutoRender = "readme_autorender"
|
||||||
FilterReadMeScripts = "filter_readme_scripts"
|
FilterReadMeScripts = "filter_readme_scripts"
|
||||||
|
|
||||||
// global
|
// global
|
||||||
HideFiles = "hide_files"
|
HideFiles = "hide_files"
|
||||||
CustomizeHead = "customize_head"
|
CustomizeHead = "customize_head"
|
||||||
@ -45,6 +46,10 @@ const (
|
|||||||
ForwardDirectLinkParams = "forward_direct_link_params"
|
ForwardDirectLinkParams = "forward_direct_link_params"
|
||||||
IgnoreDirectLinkParams = "ignore_direct_link_params"
|
IgnoreDirectLinkParams = "ignore_direct_link_params"
|
||||||
WebauthnLoginEnabled = "webauthn_login_enabled"
|
WebauthnLoginEnabled = "webauthn_login_enabled"
|
||||||
|
SharePreview = "share_preview"
|
||||||
|
ShareArchivePreview = "share_archive_preview"
|
||||||
|
ShareForceProxy = "share_force_proxy"
|
||||||
|
ShareSummaryContent = "share_summary_content"
|
||||||
|
|
||||||
// index
|
// index
|
||||||
SearchIndex = "search_index"
|
SearchIndex = "search_index"
|
||||||
@ -167,4 +172,5 @@ const (
|
|||||||
RequestHeaderKey
|
RequestHeaderKey
|
||||||
UserAgentKey
|
UserAgentKey
|
||||||
PathKey
|
PathKey
|
||||||
|
SharingIDKey
|
||||||
)
|
)
|
||||||
|
@ -25,7 +25,10 @@ var PrivacyReg []*regexp.Regexp
|
|||||||
var (
|
var (
|
||||||
// StoragesLoaded loaded success if empty
|
// StoragesLoaded loaded success if empty
|
||||||
StoragesLoaded = false
|
StoragesLoaded = false
|
||||||
|
// 单个Buffer最大限制
|
||||||
MaxBufferLimit = 16 * 1024 * 1024
|
MaxBufferLimit = 16 * 1024 * 1024
|
||||||
|
// 超过该阈值的Buffer将使用 mmap 分配,可主动释放内存
|
||||||
|
MmapThreshold = 4 * 1024 * 1024
|
||||||
)
|
)
|
||||||
var (
|
var (
|
||||||
RawIndexHtml string
|
RawIndexHtml string
|
||||||
|
@ -12,7 +12,7 @@ var db *gorm.DB
|
|||||||
|
|
||||||
func Init(d *gorm.DB) {
|
func Init(d *gorm.DB) {
|
||||||
db = d
|
db = d
|
||||||
err := AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem), new(model.SearchNode), new(model.TaskItem), new(model.SSHPublicKey))
|
err := AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem), new(model.SearchNode), new(model.TaskItem), new(model.SSHPublicKey), new(model.SharingDB))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed migrate database: %s", err.Error())
|
log.Fatalf("failed migrate database: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
62
internal/db/sharing.go
Normal file
62
internal/db/sharing.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils/random"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetSharingById(id string) (*model.SharingDB, error) {
|
||||||
|
s := model.SharingDB{ID: id}
|
||||||
|
if err := db.Where(s).First(&s).Error; err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed get sharing")
|
||||||
|
}
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSharings(pageIndex, pageSize int) (sharings []model.SharingDB, count int64, err error) {
|
||||||
|
sharingDB := db.Model(&model.SharingDB{})
|
||||||
|
if err := sharingDB.Count(&count).Error; err != nil {
|
||||||
|
return nil, 0, errors.Wrapf(err, "failed get sharings count")
|
||||||
|
}
|
||||||
|
if err := sharingDB.Order(columnName("id")).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&sharings).Error; err != nil {
|
||||||
|
return nil, 0, errors.Wrapf(err, "failed get find sharings")
|
||||||
|
}
|
||||||
|
return sharings, count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSharingsByCreatorId(creator uint, pageIndex, pageSize int) (sharings []model.SharingDB, count int64, err error) {
|
||||||
|
sharingDB := db.Model(&model.SharingDB{})
|
||||||
|
cond := model.SharingDB{CreatorId: creator}
|
||||||
|
if err := sharingDB.Where(cond).Count(&count).Error; err != nil {
|
||||||
|
return nil, 0, errors.Wrapf(err, "failed get sharings count")
|
||||||
|
}
|
||||||
|
if err := sharingDB.Where(cond).Order(columnName("id")).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&sharings).Error; err != nil {
|
||||||
|
return nil, 0, errors.Wrapf(err, "failed get find sharings")
|
||||||
|
}
|
||||||
|
return sharings, count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateSharing(s *model.SharingDB) (string, error) {
|
||||||
|
id := random.String(8)
|
||||||
|
for len(id) < 12 {
|
||||||
|
old := model.SharingDB{
|
||||||
|
ID: id,
|
||||||
|
}
|
||||||
|
if err := db.Where(old).First(&old).Error; err != nil {
|
||||||
|
s.ID = id
|
||||||
|
return id, errors.WithStack(db.Create(s).Error)
|
||||||
|
}
|
||||||
|
id += random.String(1)
|
||||||
|
}
|
||||||
|
return "", errors.New("failed find valid id")
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateSharing(s *model.SharingDB) error {
|
||||||
|
return errors.WithStack(db.Save(s).Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteSharingById(id string) error {
|
||||||
|
s := model.SharingDB{ID: id}
|
||||||
|
return errors.WithStack(db.Where(s).Delete(&s).Error)
|
||||||
|
}
|
@ -23,6 +23,10 @@ var (
|
|||||||
UnknownArchiveFormat = errors.New("unknown archive format")
|
UnknownArchiveFormat = errors.New("unknown archive format")
|
||||||
WrongArchivePassword = errors.New("wrong archive password")
|
WrongArchivePassword = errors.New("wrong archive password")
|
||||||
DriverExtractNotSupported = errors.New("driver extraction not supported")
|
DriverExtractNotSupported = errors.New("driver extraction not supported")
|
||||||
|
|
||||||
|
WrongShareCode = errors.New("wrong share code")
|
||||||
|
InvalidSharing = errors.New("invalid sharing")
|
||||||
|
SharingNotFound = errors.New("sharing not found")
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewErr wrap constant error with an extra message
|
// NewErr wrap constant error with an extra message
|
||||||
|
@ -168,7 +168,7 @@ func GetStorage(path string, args *GetStoragesArgs) (driver.Driver, error) {
|
|||||||
func Other(ctx context.Context, args model.FsOtherArgs) (interface{}, error) {
|
func Other(ctx context.Context, args model.FsOtherArgs) (interface{}, error) {
|
||||||
res, err := other(ctx, args)
|
res, err := other(ctx, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed remove %s: %+v", args.Path, err)
|
log.Errorf("failed get other %s: %+v", args.Path, err)
|
||||||
}
|
}
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
@ -77,6 +77,26 @@ type ArchiveDecompressArgs struct {
|
|||||||
PutIntoNewDir bool
|
PutIntoNewDir bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SharingListArgs struct {
|
||||||
|
Refresh bool
|
||||||
|
Pwd string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SharingArchiveMetaArgs struct {
|
||||||
|
ArchiveMetaArgs
|
||||||
|
Pwd string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SharingArchiveListArgs struct {
|
||||||
|
ArchiveListArgs
|
||||||
|
Pwd string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SharingLinkArgs struct {
|
||||||
|
Pwd string
|
||||||
|
LinkArgs
|
||||||
|
}
|
||||||
|
|
||||||
type RangeReaderIF interface {
|
type RangeReaderIF interface {
|
||||||
RangeRead(ctx context.Context, httpRange http_range.Range) (io.ReadCloser, error)
|
RangeRead(ctx context.Context, httpRange http_range.Range) (io.ReadCloser, error)
|
||||||
}
|
}
|
||||||
|
47
internal/model/sharing.go
Normal file
47
internal/model/sharing.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type SharingDB struct {
|
||||||
|
ID string `json:"id" gorm:"type:char(12);primaryKey"`
|
||||||
|
FilesRaw string `json:"-" gorm:"type:text"`
|
||||||
|
Expires *time.Time `json:"expires"`
|
||||||
|
Pwd string `json:"pwd"`
|
||||||
|
Accessed int `json:"accessed"`
|
||||||
|
MaxAccessed int `json:"max_accessed"`
|
||||||
|
CreatorId uint `json:"-"`
|
||||||
|
Disabled bool `json:"disabled"`
|
||||||
|
Remark string `json:"remark"`
|
||||||
|
Readme string `json:"readme" gorm:"type:text"`
|
||||||
|
Header string `json:"header" gorm:"type:text"`
|
||||||
|
Sort
|
||||||
|
}
|
||||||
|
|
||||||
|
type Sharing struct {
|
||||||
|
*SharingDB
|
||||||
|
Files []string `json:"files"`
|
||||||
|
Creator *User `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Sharing) Valid() bool {
|
||||||
|
if s.Disabled {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s.MaxAccessed > 0 && s.Accessed >= s.MaxAccessed {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(s.Files) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !s.Creator.CanShare() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s.Expires != nil && !s.Expires.IsZero() && s.Expires.Before(time.Now()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Sharing) Verify(pwd string) bool {
|
||||||
|
return s.Pwd == "" || s.Pwd == pwd
|
||||||
|
}
|
@ -54,6 +54,7 @@ type User struct {
|
|||||||
// 11: ftp/sftp write
|
// 11: ftp/sftp write
|
||||||
// 12: can read archives
|
// 12: can read archives
|
||||||
// 13: can decompress archives
|
// 13: can decompress archives
|
||||||
|
// 14: can share
|
||||||
Permission int32 `json:"permission"`
|
Permission int32 `json:"permission"`
|
||||||
OtpSecret string `json:"-"`
|
OtpSecret string `json:"-"`
|
||||||
SsoID string `json:"sso_id"` // unique by sso platform
|
SsoID string `json:"sso_id"` // unique by sso platform
|
||||||
@ -145,6 +146,10 @@ func (u *User) CanDecompress() bool {
|
|||||||
return (u.Permission>>13)&1 == 1
|
return (u.Permission>>13)&1 == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) CanShare() bool {
|
||||||
|
return (u.Permission>>14)&1 == 1
|
||||||
|
}
|
||||||
|
|
||||||
func (u *User) JoinPath(reqPath string) (string, error) {
|
func (u *User) JoinPath(reqPath string) (string, error) {
|
||||||
return utils.JoinBasePath(u.BasePath, reqPath)
|
return utils.JoinBasePath(u.BasePath, reqPath)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package net
|
package net
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -15,6 +14,7 @@ import (
|
|||||||
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||||
|
"github.com/rclone/rclone/lib/mmap"
|
||||||
|
|
||||||
"github.com/OpenListTeam/OpenList/v4/pkg/http_range"
|
"github.com/OpenListTeam/OpenList/v4/pkg/http_range"
|
||||||
"github.com/aws/aws-sdk-go/aws/awsutil"
|
"github.com/aws/aws-sdk-go/aws/awsutil"
|
||||||
@ -255,7 +255,10 @@ func (d *downloader) sendChunkTask(newConcurrency bool) error {
|
|||||||
finalSize += firstSize - minSize
|
finalSize += firstSize - minSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buf.Reset(int(finalSize))
|
err := buf.Reset(int(finalSize))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
ch := chunk{
|
ch := chunk{
|
||||||
start: d.pos,
|
start: d.pos,
|
||||||
size: finalSize,
|
size: finalSize,
|
||||||
@ -645,11 +648,13 @@ func (mr MultiReadCloser) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Buf struct {
|
type Buf struct {
|
||||||
buffer *bytes.Buffer
|
|
||||||
size int //expected size
|
size int //expected size
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
off int
|
offR int
|
||||||
|
offW int
|
||||||
rw sync.Mutex
|
rw sync.Mutex
|
||||||
|
buf []byte
|
||||||
|
mmap bool
|
||||||
|
|
||||||
readSignal chan struct{}
|
readSignal chan struct{}
|
||||||
readPending bool
|
readPending bool
|
||||||
@ -658,54 +663,62 @@ type Buf struct {
|
|||||||
// NewBuf is a buffer that can have 1 read & 1 write at the same time.
|
// NewBuf is a buffer that can have 1 read & 1 write at the same time.
|
||||||
// when read is faster write, immediately feed data to read after written
|
// when read is faster write, immediately feed data to read after written
|
||||||
func NewBuf(ctx context.Context, maxSize int) *Buf {
|
func NewBuf(ctx context.Context, maxSize int) *Buf {
|
||||||
return &Buf{
|
br := &Buf{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
buffer: bytes.NewBuffer(make([]byte, 0, maxSize)),
|
|
||||||
size: maxSize,
|
size: maxSize,
|
||||||
|
|
||||||
readSignal: make(chan struct{}, 1),
|
readSignal: make(chan struct{}, 1),
|
||||||
}
|
}
|
||||||
|
if conf.MmapThreshold > 0 && maxSize >= conf.MmapThreshold {
|
||||||
|
m, err := mmap.Alloc(maxSize)
|
||||||
|
if err == nil {
|
||||||
|
br.buf = m
|
||||||
|
br.mmap = true
|
||||||
|
return br
|
||||||
}
|
}
|
||||||
func (br *Buf) Reset(size int) {
|
|
||||||
br.rw.Lock()
|
|
||||||
defer br.rw.Unlock()
|
|
||||||
if br.buffer == nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
br.buffer.Reset()
|
br.buf = make([]byte, maxSize)
|
||||||
br.size = size
|
return br
|
||||||
br.off = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (br *Buf) Read(p []byte) (n int, err error) {
|
func (br *Buf) Reset(size int) error {
|
||||||
|
br.rw.Lock()
|
||||||
|
defer br.rw.Unlock()
|
||||||
|
if br.buf == nil {
|
||||||
|
return io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
if size > cap(br.buf) {
|
||||||
|
return fmt.Errorf("reset size %d exceeds max size %d", size, cap(br.buf))
|
||||||
|
}
|
||||||
|
br.size = size
|
||||||
|
br.offR = 0
|
||||||
|
br.offW = 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *Buf) Read(p []byte) (int, error) {
|
||||||
if err := br.ctx.Err(); err != nil {
|
if err := br.ctx.Err(); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
if len(p) == 0 {
|
if len(p) == 0 {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
if br.off >= br.size {
|
if br.offR >= br.size {
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
br.rw.Lock()
|
br.rw.Lock()
|
||||||
if br.buffer != nil {
|
if br.buf == nil {
|
||||||
n, err = br.buffer.Read(p)
|
|
||||||
} else {
|
|
||||||
err = io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
br.rw.Unlock()
|
br.rw.Unlock()
|
||||||
return
|
return 0, io.ErrClosedPipe
|
||||||
}
|
}
|
||||||
if n > 0 {
|
|
||||||
br.off += n
|
if br.offW < br.offR {
|
||||||
br.rw.Unlock()
|
br.rw.Unlock()
|
||||||
return n, nil
|
return 0, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
|
if br.offW == br.offR {
|
||||||
br.readPending = true
|
br.readPending = true
|
||||||
br.rw.Unlock()
|
br.rw.Unlock()
|
||||||
// n==0, err==io.EOF
|
|
||||||
select {
|
select {
|
||||||
case <-br.ctx.Done():
|
case <-br.ctx.Done():
|
||||||
return 0, br.ctx.Err()
|
return 0, br.ctx.Err()
|
||||||
@ -716,18 +729,34 @@ func (br *Buf) Read(p []byte) (n int, err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
n := copy(p, br.buf[br.offR:br.offW])
|
||||||
|
br.offR += n
|
||||||
|
br.rw.Unlock()
|
||||||
|
if n < len(p) && br.offR >= br.size {
|
||||||
|
return n, io.EOF
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (br *Buf) Write(p []byte) (n int, err error) {
|
func (br *Buf) Write(p []byte) (int, error) {
|
||||||
if err := br.ctx.Err(); err != nil {
|
if err := br.ctx.Err(); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
if len(p) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
br.rw.Lock()
|
br.rw.Lock()
|
||||||
defer br.rw.Unlock()
|
defer br.rw.Unlock()
|
||||||
if br.buffer == nil {
|
if br.buf == nil {
|
||||||
return 0, io.ErrClosedPipe
|
return 0, io.ErrClosedPipe
|
||||||
}
|
}
|
||||||
n, err = br.buffer.Write(p)
|
if br.offW >= br.size {
|
||||||
|
return 0, io.ErrShortWrite
|
||||||
|
}
|
||||||
|
n := copy(br.buf[br.offW:], p[:min(br.size-br.offW, len(p))])
|
||||||
|
br.offW += n
|
||||||
if br.readPending {
|
if br.readPending {
|
||||||
br.readPending = false
|
br.readPending = false
|
||||||
select {
|
select {
|
||||||
@ -735,12 +764,21 @@ func (br *Buf) Write(p []byte) (n int, err error) {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
if n < len(p) {
|
||||||
|
return n, io.ErrShortWrite
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (br *Buf) Close() {
|
func (br *Buf) Close() error {
|
||||||
br.rw.Lock()
|
br.rw.Lock()
|
||||||
defer br.rw.Unlock()
|
defer br.rw.Unlock()
|
||||||
br.buffer = nil
|
var err error
|
||||||
close(br.readSignal)
|
if br.mmap {
|
||||||
|
err = mmap.Free(br.buf)
|
||||||
|
br.mmap = false
|
||||||
|
}
|
||||||
|
br.buf = nil
|
||||||
|
close(br.readSignal)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
139
internal/op/sharing.go
Normal file
139
internal/op/sharing.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
package op
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
stdpath "path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/db"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/pkg/singleflight"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||||
|
"github.com/OpenListTeam/go-cache"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeJoined(sdb []model.SharingDB) []model.Sharing {
|
||||||
|
creator := make(map[uint]*model.User)
|
||||||
|
return utils.MustSliceConvert(sdb, func(s model.SharingDB) model.Sharing {
|
||||||
|
var c *model.User
|
||||||
|
var ok bool
|
||||||
|
if c, ok = creator[s.CreatorId]; !ok {
|
||||||
|
var err error
|
||||||
|
if c, err = GetUserById(s.CreatorId); err != nil {
|
||||||
|
c = nil
|
||||||
|
} else {
|
||||||
|
creator[s.CreatorId] = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var files []string
|
||||||
|
if err := utils.Json.UnmarshalFromString(s.FilesRaw, &files); err != nil {
|
||||||
|
files = make([]string, 0)
|
||||||
|
}
|
||||||
|
return model.Sharing{
|
||||||
|
SharingDB: &s,
|
||||||
|
Files: files,
|
||||||
|
Creator: c,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var sharingCache = cache.NewMemCache(cache.WithShards[*model.Sharing](8))
|
||||||
|
var sharingG singleflight.Group[*model.Sharing]
|
||||||
|
|
||||||
|
func GetSharingById(id string, refresh ...bool) (*model.Sharing, error) {
|
||||||
|
if !utils.IsBool(refresh...) {
|
||||||
|
if sharing, ok := sharingCache.Get(id); ok {
|
||||||
|
log.Debugf("use cache when get sharing %s", id)
|
||||||
|
return sharing, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sharing, err, _ := sharingG.Do(id, func() (*model.Sharing, error) {
|
||||||
|
s, err := db.GetSharingById(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessagef(err, "failed get sharing [%s]", id)
|
||||||
|
}
|
||||||
|
creator, err := GetUserById(s.CreatorId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithMessagef(err, "failed get sharing creator [%s]", id)
|
||||||
|
}
|
||||||
|
var files []string
|
||||||
|
if err = utils.Json.UnmarshalFromString(s.FilesRaw, &files); err != nil {
|
||||||
|
files = make([]string, 0)
|
||||||
|
}
|
||||||
|
return &model.Sharing{
|
||||||
|
SharingDB: s,
|
||||||
|
Files: files,
|
||||||
|
Creator: creator,
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
return sharing, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSharings(pageIndex, pageSize int) ([]model.Sharing, int64, error) {
|
||||||
|
s, cnt, err := db.GetSharings(pageIndex, pageSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return makeJoined(s), cnt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSharingsByCreatorId(userId uint, pageIndex, pageSize int) ([]model.Sharing, int64, error) {
|
||||||
|
s, cnt, err := db.GetSharingsByCreatorId(userId, pageIndex, pageSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return makeJoined(s), cnt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSharingUnwrapPath(sharing *model.Sharing, path string) (unwrapPath string, err error) {
|
||||||
|
if len(sharing.Files) == 0 {
|
||||||
|
return "", errors.New("cannot get actual path of an invalid sharing")
|
||||||
|
}
|
||||||
|
if len(sharing.Files) == 1 {
|
||||||
|
return stdpath.Join(sharing.Files[0], path), nil
|
||||||
|
}
|
||||||
|
path = utils.FixAndCleanPath(path)[1:]
|
||||||
|
if len(path) == 0 {
|
||||||
|
return "", errors.New("cannot get actual path of a sharing root path")
|
||||||
|
}
|
||||||
|
mapPath := ""
|
||||||
|
child, rest, _ := strings.Cut(path, "/")
|
||||||
|
for _, c := range sharing.Files {
|
||||||
|
if child == stdpath.Base(c) {
|
||||||
|
mapPath = c
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mapPath == "" {
|
||||||
|
return "", fmt.Errorf("failed find child [%s] of sharing [%s]", child, sharing.ID)
|
||||||
|
}
|
||||||
|
return stdpath.Join(mapPath, rest), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateSharing(sharing *model.Sharing) (id string, err error) {
|
||||||
|
sharing.CreatorId = sharing.Creator.ID
|
||||||
|
sharing.FilesRaw, err = utils.Json.MarshalToString(utils.MustSliceConvert(sharing.Files, utils.FixAndCleanPath))
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return db.CreateSharing(sharing.SharingDB)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateSharing(sharing *model.Sharing, skipMarshal ...bool) (err error) {
|
||||||
|
if !utils.IsBool(skipMarshal...) {
|
||||||
|
sharing.CreatorId = sharing.Creator.ID
|
||||||
|
sharing.FilesRaw, err = utils.Json.MarshalToString(utils.MustSliceConvert(sharing.Files, utils.FixAndCleanPath))
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sharingCache.Del(sharing.ID)
|
||||||
|
return db.UpdateSharing(sharing.SharingDB)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteSharing(sid string) error {
|
||||||
|
sharingCache.Del(sid)
|
||||||
|
return db.DeleteSharingById(sid)
|
||||||
|
}
|
65
internal/sharing/archive.go
Normal file
65
internal/sharing/archive.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package sharing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func archiveMeta(ctx context.Context, sid, path string, args model.SharingArchiveMetaArgs) (*model.Sharing, *model.ArchiveMetaProvider, error) {
|
||||||
|
sharing, err := op.GetSharingById(sid, args.Refresh)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.WithStack(errs.SharingNotFound)
|
||||||
|
}
|
||||||
|
if !sharing.Valid() {
|
||||||
|
return sharing, nil, errors.WithStack(errs.InvalidSharing)
|
||||||
|
}
|
||||||
|
if !sharing.Verify(args.Pwd) {
|
||||||
|
return sharing, nil, errors.WithStack(errs.WrongShareCode)
|
||||||
|
}
|
||||||
|
path = utils.FixAndCleanPath(path)
|
||||||
|
if len(sharing.Files) == 1 || path != "/" {
|
||||||
|
unwrapPath, err := op.GetSharingUnwrapPath(sharing, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.WithMessage(err, "failed get sharing unwrap path")
|
||||||
|
}
|
||||||
|
storage, actualPath, err := op.GetStorageAndActualPath(unwrapPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.WithMessage(err, "failed get sharing file")
|
||||||
|
}
|
||||||
|
obj, err := op.GetArchiveMeta(ctx, storage, actualPath, args.ArchiveMetaArgs)
|
||||||
|
return sharing, obj, err
|
||||||
|
}
|
||||||
|
return nil, nil, errors.New("cannot get sharing root archive meta")
|
||||||
|
}
|
||||||
|
|
||||||
|
func archiveList(ctx context.Context, sid, path string, args model.SharingArchiveListArgs) (*model.Sharing, []model.Obj, error) {
|
||||||
|
sharing, err := op.GetSharingById(sid, args.Refresh)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.WithStack(errs.SharingNotFound)
|
||||||
|
}
|
||||||
|
if !sharing.Valid() {
|
||||||
|
return sharing, nil, errors.WithStack(errs.InvalidSharing)
|
||||||
|
}
|
||||||
|
if !sharing.Verify(args.Pwd) {
|
||||||
|
return sharing, nil, errors.WithStack(errs.WrongShareCode)
|
||||||
|
}
|
||||||
|
path = utils.FixAndCleanPath(path)
|
||||||
|
if len(sharing.Files) == 1 || path != "/" {
|
||||||
|
unwrapPath, err := op.GetSharingUnwrapPath(sharing, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.WithMessage(err, "failed get sharing unwrap path")
|
||||||
|
}
|
||||||
|
storage, actualPath, err := op.GetStorageAndActualPath(unwrapPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.WithMessage(err, "failed get sharing file")
|
||||||
|
}
|
||||||
|
obj, err := op.ListArchive(ctx, storage, actualPath, args.ArchiveListArgs)
|
||||||
|
return sharing, obj, err
|
||||||
|
}
|
||||||
|
return nil, nil, errors.New("cannot get sharing root archive list")
|
||||||
|
}
|
60
internal/sharing/get.go
Normal file
60
internal/sharing/get.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package sharing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
stdpath "path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func get(ctx context.Context, sid, path string, args model.SharingListArgs) (*model.Sharing, model.Obj, error) {
|
||||||
|
sharing, err := op.GetSharingById(sid, args.Refresh)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.WithStack(errs.SharingNotFound)
|
||||||
|
}
|
||||||
|
if !sharing.Valid() {
|
||||||
|
return sharing, nil, errors.WithStack(errs.InvalidSharing)
|
||||||
|
}
|
||||||
|
if !sharing.Verify(args.Pwd) {
|
||||||
|
return sharing, nil, errors.WithStack(errs.WrongShareCode)
|
||||||
|
}
|
||||||
|
path = utils.FixAndCleanPath(path)
|
||||||
|
if len(sharing.Files) == 1 || path != "/" {
|
||||||
|
unwrapPath, err := op.GetSharingUnwrapPath(sharing, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.WithMessage(err, "failed get sharing unwrap path")
|
||||||
|
}
|
||||||
|
if unwrapPath != "/" {
|
||||||
|
virtualFiles := op.GetStorageVirtualFilesByPath(stdpath.Dir(unwrapPath))
|
||||||
|
for _, f := range virtualFiles {
|
||||||
|
if f.GetName() == stdpath.Base(unwrapPath) {
|
||||||
|
return sharing, f, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return sharing, &model.Object{
|
||||||
|
Name: sid,
|
||||||
|
Size: 0,
|
||||||
|
Modified: time.Time{},
|
||||||
|
IsFolder: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
storage, actualPath, err := op.GetStorageAndActualPath(unwrapPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.WithMessage(err, "failed get sharing file")
|
||||||
|
}
|
||||||
|
obj, err := op.Get(ctx, storage, actualPath)
|
||||||
|
return sharing, obj, err
|
||||||
|
}
|
||||||
|
return sharing, &model.Object{
|
||||||
|
Name: sid,
|
||||||
|
Size: 0,
|
||||||
|
Modified: time.Time{},
|
||||||
|
IsFolder: true,
|
||||||
|
}, nil
|
||||||
|
}
|
46
internal/sharing/link.go
Normal file
46
internal/sharing/link.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package sharing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/server/common"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func link(ctx context.Context, sid, path string, args *LinkArgs) (*model.Sharing, *model.Link, model.Obj, error) {
|
||||||
|
sharing, err := op.GetSharingById(sid, args.SharingListArgs.Refresh)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, errors.WithStack(errs.SharingNotFound)
|
||||||
|
}
|
||||||
|
if !sharing.Valid() {
|
||||||
|
return sharing, nil, nil, errors.WithStack(errs.InvalidSharing)
|
||||||
|
}
|
||||||
|
if !sharing.Verify(args.Pwd) {
|
||||||
|
return sharing, nil, nil, errors.WithStack(errs.WrongShareCode)
|
||||||
|
}
|
||||||
|
path = utils.FixAndCleanPath(path)
|
||||||
|
if len(sharing.Files) == 1 || path != "/" {
|
||||||
|
unwrapPath, err := op.GetSharingUnwrapPath(sharing, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, errors.WithMessage(err, "failed get sharing unwrap path")
|
||||||
|
}
|
||||||
|
storage, actualPath, err := op.GetStorageAndActualPath(unwrapPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, errors.WithMessage(err, "failed get sharing link")
|
||||||
|
}
|
||||||
|
l, obj, err := op.Link(ctx, storage, actualPath, args.LinkArgs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, errors.WithMessage(err, "failed get sharing link")
|
||||||
|
}
|
||||||
|
if l.URL != "" && !strings.HasPrefix(l.URL, "http://") && !strings.HasPrefix(l.URL, "https://") {
|
||||||
|
l.URL = common.GetApiUrl(ctx) + l.URL
|
||||||
|
}
|
||||||
|
return sharing, l, obj, nil
|
||||||
|
}
|
||||||
|
return nil, nil, nil, errors.New("cannot get sharing root link")
|
||||||
|
}
|
83
internal/sharing/list.go
Normal file
83
internal/sharing/list.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package sharing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
stdpath "path"
|
||||||
|
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func list(ctx context.Context, sid, path string, args model.SharingListArgs) (*model.Sharing, []model.Obj, error) {
|
||||||
|
sharing, err := op.GetSharingById(sid, args.Refresh)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.WithStack(errs.SharingNotFound)
|
||||||
|
}
|
||||||
|
if !sharing.Valid() {
|
||||||
|
return sharing, nil, errors.WithStack(errs.InvalidSharing)
|
||||||
|
}
|
||||||
|
if !sharing.Verify(args.Pwd) {
|
||||||
|
return sharing, nil, errors.WithStack(errs.WrongShareCode)
|
||||||
|
}
|
||||||
|
path = utils.FixAndCleanPath(path)
|
||||||
|
if len(sharing.Files) == 1 || path != "/" {
|
||||||
|
unwrapPath, err := op.GetSharingUnwrapPath(sharing, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.WithMessage(err, "failed get sharing unwrap path")
|
||||||
|
}
|
||||||
|
virtualFiles := op.GetStorageVirtualFilesByPath(unwrapPath)
|
||||||
|
storage, actualPath, err := op.GetStorageAndActualPath(unwrapPath)
|
||||||
|
if err != nil && len(virtualFiles) == 0 {
|
||||||
|
return nil, nil, errors.WithMessage(err, "failed list sharing")
|
||||||
|
}
|
||||||
|
var objs []model.Obj
|
||||||
|
if storage != nil {
|
||||||
|
objs, err = op.List(ctx, storage, actualPath, model.ListArgs{
|
||||||
|
Refresh: args.Refresh,
|
||||||
|
ReqPath: stdpath.Join(sid, path),
|
||||||
|
})
|
||||||
|
if err != nil && len(virtualFiles) == 0 {
|
||||||
|
return nil, nil, errors.WithMessage(err, "failed list sharing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
om := model.NewObjMerge()
|
||||||
|
objs = om.Merge(objs, virtualFiles...)
|
||||||
|
model.SortFiles(objs, sharing.OrderBy, sharing.OrderDirection)
|
||||||
|
model.ExtractFolder(objs, sharing.ExtractFolder)
|
||||||
|
return sharing, objs, nil
|
||||||
|
}
|
||||||
|
objs := make([]model.Obj, 0, len(sharing.Files))
|
||||||
|
for _, f := range sharing.Files {
|
||||||
|
if f != "/" {
|
||||||
|
isVf := false
|
||||||
|
virtualFiles := op.GetStorageVirtualFilesByPath(stdpath.Dir(f))
|
||||||
|
for _, vf := range virtualFiles {
|
||||||
|
if vf.GetName() == stdpath.Base(f) {
|
||||||
|
objs = append(objs, vf)
|
||||||
|
isVf = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isVf {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
storage, actualPath, err := op.GetStorageAndActualPath(f)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
obj, err := op.Get(ctx, storage, actualPath)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
objs = append(objs, obj)
|
||||||
|
}
|
||||||
|
model.SortFiles(objs, sharing.OrderBy, sharing.OrderDirection)
|
||||||
|
model.ExtractFolder(objs, sharing.ExtractFolder)
|
||||||
|
return sharing, objs, nil
|
||||||
|
}
|
58
internal/sharing/sharing.go
Normal file
58
internal/sharing/sharing.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package sharing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func List(ctx context.Context, sid, path string, args model.SharingListArgs) (*model.Sharing, []model.Obj, error) {
|
||||||
|
sharing, res, err := list(ctx, sid, path, args)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed list sharing %s/%s: %+v", sid, path, err)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return sharing, res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(ctx context.Context, sid, path string, args model.SharingListArgs) (*model.Sharing, model.Obj, error) {
|
||||||
|
sharing, res, err := get(ctx, sid, path, args)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed get sharing %s/%s: %s", sid, path, err)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return sharing, res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ArchiveMeta(ctx context.Context, sid, path string, args model.SharingArchiveMetaArgs) (*model.Sharing, *model.ArchiveMetaProvider, error) {
|
||||||
|
sharing, res, err := archiveMeta(ctx, sid, path, args)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed get sharing archive meta %s/%s: %s", sid, path, err)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return sharing, res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ArchiveList(ctx context.Context, sid, path string, args model.SharingArchiveListArgs) (*model.Sharing, []model.Obj, error) {
|
||||||
|
sharing, res, err := archiveList(ctx, sid, path, args)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed list sharing archive %s/%s: %s", sid, path, err)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return sharing, res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type LinkArgs struct {
|
||||||
|
model.SharingListArgs
|
||||||
|
model.LinkArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func Link(ctx context.Context, sid, path string, args *LinkArgs) (*model.Sharing, *model.Link, model.Obj, error) {
|
||||||
|
sharing, res, file, err := link(ctx, sid, path, args)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed get sharing link %s/%s: %+v", sid, path, err)
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
return sharing, res, file, nil
|
||||||
|
}
|
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/OpenListTeam/OpenList/v4/pkg/buffer"
|
"github.com/OpenListTeam/OpenList/v4/pkg/buffer"
|
||||||
"github.com/OpenListTeam/OpenList/v4/pkg/http_range"
|
"github.com/OpenListTeam/OpenList/v4/pkg/http_range"
|
||||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||||
|
"github.com/rclone/rclone/lib/mmap"
|
||||||
"go4.org/readerutil"
|
"go4.org/readerutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -60,8 +61,12 @@ func (f *FileStream) IsForceStreamUpload() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *FileStream) Close() error {
|
func (f *FileStream) Close() error {
|
||||||
var err1, err2 error
|
if f.peekBuff != nil {
|
||||||
|
f.peekBuff.Reset()
|
||||||
|
f.peekBuff = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err1, err2 error
|
||||||
err1 = f.Closers.Close()
|
err1 = f.Closers.Close()
|
||||||
if errors.Is(err1, os.ErrClosed) {
|
if errors.Is(err1, os.ErrClosed) {
|
||||||
err1 = nil
|
err1 = nil
|
||||||
@ -74,10 +79,6 @@ func (f *FileStream) Close() error {
|
|||||||
f.tmpFile = nil
|
f.tmpFile = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if f.peekBuff != nil {
|
|
||||||
f.peekBuff.Reset()
|
|
||||||
f.peekBuff = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.Join(err1, err2)
|
return errors.Join(err1, err2)
|
||||||
}
|
}
|
||||||
@ -194,7 +195,19 @@ func (f *FileStream) cache(maxCacheSize int64) (model.File, error) {
|
|||||||
f.oriReader = f.Reader
|
f.oriReader = f.Reader
|
||||||
}
|
}
|
||||||
bufSize := maxCacheSize - int64(f.peekBuff.Len())
|
bufSize := maxCacheSize - int64(f.peekBuff.Len())
|
||||||
buf := make([]byte, bufSize)
|
var buf []byte
|
||||||
|
if conf.MmapThreshold > 0 && bufSize >= int64(conf.MmapThreshold) {
|
||||||
|
m, err := mmap.Alloc(int(bufSize))
|
||||||
|
if err == nil {
|
||||||
|
f.Add(utils.CloseFunc(func() error {
|
||||||
|
return mmap.Free(m)
|
||||||
|
}))
|
||||||
|
buf = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if buf == nil {
|
||||||
|
buf = make([]byte, bufSize)
|
||||||
|
}
|
||||||
n, err := io.ReadFull(f.oriReader, buf)
|
n, err := io.ReadFull(f.oriReader, buf)
|
||||||
if bufSize != int64(n) {
|
if bufSize != int64(n) {
|
||||||
return nil, fmt.Errorf("failed to read all data: (expect =%d, actual =%d) %w", bufSize, n, err)
|
return nil, fmt.Errorf("failed to read all data: (expect =%d, actual =%d) %w", bufSize, n, err)
|
||||||
|
@ -7,11 +7,13 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||||
"github.com/OpenListTeam/OpenList/v4/pkg/http_range"
|
"github.com/OpenListTeam/OpenList/v4/pkg/http_range"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFileStream_RangeRead(t *testing.T) {
|
func TestFileStream_RangeRead(t *testing.T) {
|
||||||
|
conf.MaxBufferLimit = 16 * 1024 * 1024
|
||||||
type args struct {
|
type args struct {
|
||||||
httpRange http_range.Range
|
httpRange http_range.Range
|
||||||
}
|
}
|
||||||
@ -71,7 +73,7 @@ func TestFileStream_RangeRead(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
t.Run("after check", func(t *testing.T) {
|
t.Run("after", func(t *testing.T) {
|
||||||
if f.GetFile() == nil {
|
if f.GetFile() == nil {
|
||||||
t.Error("not cached")
|
t.Error("not cached")
|
||||||
}
|
}
|
||||||
|
@ -8,13 +8,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/net"
|
"github.com/OpenListTeam/OpenList/v4/internal/net"
|
||||||
"github.com/OpenListTeam/OpenList/v4/pkg/http_range"
|
"github.com/OpenListTeam/OpenList/v4/pkg/http_range"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/pkg/pool"
|
||||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||||
|
"github.com/rclone/rclone/lib/mmap"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -153,26 +154,49 @@ func CacheFullAndHash(stream model.FileStreamer, up *model.UpdateProgress, hashT
|
|||||||
type StreamSectionReader struct {
|
type StreamSectionReader struct {
|
||||||
file model.FileStreamer
|
file model.FileStreamer
|
||||||
off int64
|
off int64
|
||||||
bufPool *sync.Pool
|
bufPool *pool.Pool[[]byte]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStreamSectionReader(file model.FileStreamer, maxBufferSize int, up *model.UpdateProgress) (*StreamSectionReader, error) {
|
func NewStreamSectionReader(file model.FileStreamer, maxBufferSize int, up *model.UpdateProgress) (*StreamSectionReader, error) {
|
||||||
ss := &StreamSectionReader{file: file}
|
ss := &StreamSectionReader{file: file}
|
||||||
if file.GetFile() == nil {
|
if file.GetFile() != nil {
|
||||||
|
return ss, nil
|
||||||
|
}
|
||||||
|
|
||||||
maxBufferSize = min(maxBufferSize, int(file.GetSize()))
|
maxBufferSize = min(maxBufferSize, int(file.GetSize()))
|
||||||
if maxBufferSize > conf.MaxBufferLimit {
|
if maxBufferSize > conf.MaxBufferLimit {
|
||||||
_, err := file.CacheFullAndWriter(up, nil)
|
_, err := file.CacheFullAndWriter(up, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return ss, nil
|
||||||
|
}
|
||||||
|
if conf.MmapThreshold > 0 && maxBufferSize >= conf.MmapThreshold {
|
||||||
|
ss.bufPool = &pool.Pool[[]byte]{
|
||||||
|
New: func() []byte {
|
||||||
|
buf, err := mmap.Alloc(maxBufferSize)
|
||||||
|
if err == nil {
|
||||||
|
file.Add(utils.CloseFunc(func() error {
|
||||||
|
return mmap.Free(buf)
|
||||||
|
}))
|
||||||
} else {
|
} else {
|
||||||
ss.bufPool = &sync.Pool{
|
buf = make([]byte, maxBufferSize)
|
||||||
New: func() any {
|
}
|
||||||
|
return buf
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ss.bufPool = &pool.Pool[[]byte]{
|
||||||
|
New: func() []byte {
|
||||||
return make([]byte, maxBufferSize)
|
return make([]byte, maxBufferSize)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
file.Add(utils.CloseFunc(func() error {
|
||||||
|
ss.bufPool.Reset()
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
return ss, nil
|
return ss, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,7 +208,7 @@ func (ss *StreamSectionReader) GetSectionReader(off, length int64) (*SectionRead
|
|||||||
if off != ss.off {
|
if off != ss.off {
|
||||||
return nil, fmt.Errorf("stream not cached: request offset %d != current offset %d", off, ss.off)
|
return nil, fmt.Errorf("stream not cached: request offset %d != current offset %d", off, ss.off)
|
||||||
}
|
}
|
||||||
tempBuf := ss.bufPool.Get().([]byte)
|
tempBuf := ss.bufPool.Get()
|
||||||
buf = tempBuf[:length]
|
buf = tempBuf[:length]
|
||||||
n, err := io.ReadFull(ss.file, buf)
|
n, err := io.ReadFull(ss.file, buf)
|
||||||
if int64(n) != length {
|
if int64(n) != length {
|
||||||
|
37
pkg/pool/pool.go
Normal file
37
pkg/pool/pool.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package pool
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type Pool[T any] struct {
|
||||||
|
New func() T
|
||||||
|
MaxCap int
|
||||||
|
|
||||||
|
cache []T
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool[T]) Get() T {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
if len(p.cache) == 0 {
|
||||||
|
return p.New()
|
||||||
|
}
|
||||||
|
item := p.cache[len(p.cache)-1]
|
||||||
|
p.cache = p.cache[:len(p.cache)-1]
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool[T]) Put(item T) {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
if p.MaxCap == 0 || len(p.cache) < int(p.MaxCap) {
|
||||||
|
p.cache = append(p.cache, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool[T]) Reset() {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
clear(p.cache)
|
||||||
|
p.cache = nil
|
||||||
|
}
|
@ -2,6 +2,9 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/OpenListTeam/OpenList/v4/cmd/flags"
|
"github.com/OpenListTeam/OpenList/v4/cmd/flags"
|
||||||
@ -38,6 +41,41 @@ func ErrorResp(c *gin.Context, err error, code int, l ...bool) {
|
|||||||
//c.Abort()
|
//c.Abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrorPage is used to return error page HTML.
|
||||||
|
// It also returns standard HTTP status code.
|
||||||
|
// @param l: if true, log error
|
||||||
|
func ErrorPage(c *gin.Context, err error, code int, l ...bool) {
|
||||||
|
|
||||||
|
if len(l) > 0 && l[0] {
|
||||||
|
if flags.Debug || flags.Dev {
|
||||||
|
log.Errorf("%+v", err)
|
||||||
|
} else {
|
||||||
|
log.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
codes := fmt.Sprintf("%d %s", code, http.StatusText(code))
|
||||||
|
|
||||||
|
html := fmt.Sprintf(`<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="color-scheme" content="dark light" />
|
||||||
|
<meta name="robots" content="noindex" />
|
||||||
|
<title>%s</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>%s</h1>
|
||||||
|
<hr>
|
||||||
|
<p>%s</p>
|
||||||
|
</body>
|
||||||
|
</html>`,
|
||||||
|
codes, codes, html.EscapeString(hidePrivacy(err.Error())))
|
||||||
|
c.Data(code, "text/html; charset=utf-8", []byte(html))
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
|
||||||
func ErrorWithDataResp(c *gin.Context, err error, code int, data interface{}, l ...bool) {
|
func ErrorWithDataResp(c *gin.Context, err error, code int, data interface{}, l ...bool) {
|
||||||
if len(l) > 0 && l[0] {
|
if len(l) > 0 && l[0] {
|
||||||
if flags.Debug || flags.Dev {
|
if flags.Debug || flags.Dev {
|
||||||
|
@ -3,9 +3,9 @@ package handles
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
stdpath "path"
|
stdpath "path"
|
||||||
|
"strings"
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/task"
|
|
||||||
|
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/archive/tool"
|
"github.com/OpenListTeam/OpenList/v4/internal/archive/tool"
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
||||||
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/setting"
|
"github.com/OpenListTeam/OpenList/v4/internal/setting"
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/sign"
|
"github.com/OpenListTeam/OpenList/v4/internal/sign"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/task"
|
||||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||||
"github.com/OpenListTeam/OpenList/v4/server/common"
|
"github.com/OpenListTeam/OpenList/v4/server/common"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@ -71,13 +72,26 @@ func toContentResp(objs []model.ObjTree) []ArchiveContentResp {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func FsArchiveMeta(c *gin.Context) {
|
func FsArchiveMetaSplit(c *gin.Context) {
|
||||||
var req ArchiveMetaReq
|
var req ArchiveMetaReq
|
||||||
if err := c.ShouldBind(&req); err != nil {
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
common.ErrorResp(c, err, 400)
|
common.ErrorResp(c, err, 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(req.Path, "/@s") {
|
||||||
|
req.Path = strings.TrimPrefix(req.Path, "/@s")
|
||||||
|
SharingArchiveMeta(c, &req)
|
||||||
|
return
|
||||||
|
}
|
||||||
user := c.Request.Context().Value(conf.UserKey).(*model.User)
|
user := c.Request.Context().Value(conf.UserKey).(*model.User)
|
||||||
|
if user.IsGuest() && user.Disabled {
|
||||||
|
common.ErrorStrResp(c, "Guest user is disabled, login please", 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
FsArchiveMeta(c, &req, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FsArchiveMeta(c *gin.Context, req *ArchiveMetaReq, user *model.User) {
|
||||||
if !user.CanReadArchives() {
|
if !user.CanReadArchives() {
|
||||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
return
|
return
|
||||||
@ -142,19 +156,27 @@ type ArchiveListReq struct {
|
|||||||
InnerPath string `json:"inner_path" form:"inner_path"`
|
InnerPath string `json:"inner_path" form:"inner_path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ArchiveListResp struct {
|
func FsArchiveListSplit(c *gin.Context) {
|
||||||
Content []ObjResp `json:"content"`
|
|
||||||
Total int64 `json:"total"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func FsArchiveList(c *gin.Context) {
|
|
||||||
var req ArchiveListReq
|
var req ArchiveListReq
|
||||||
if err := c.ShouldBind(&req); err != nil {
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
common.ErrorResp(c, err, 400)
|
common.ErrorResp(c, err, 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req.Validate()
|
req.Validate()
|
||||||
|
if strings.HasPrefix(req.Path, "/@s") {
|
||||||
|
req.Path = strings.TrimPrefix(req.Path, "/@s")
|
||||||
|
SharingArchiveList(c, &req)
|
||||||
|
return
|
||||||
|
}
|
||||||
user := c.Request.Context().Value(conf.UserKey).(*model.User)
|
user := c.Request.Context().Value(conf.UserKey).(*model.User)
|
||||||
|
if user.IsGuest() && user.Disabled {
|
||||||
|
common.ErrorStrResp(c, "Guest user is disabled, login please", 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
FsArchiveList(c, &req, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FsArchiveList(c *gin.Context, req *ArchiveListReq, user *model.User) {
|
||||||
if !user.CanReadArchives() {
|
if !user.CanReadArchives() {
|
||||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
return
|
return
|
||||||
@ -201,7 +223,7 @@ func FsArchiveList(c *gin.Context) {
|
|||||||
ret, _ := utils.SliceConvert(objs, func(src model.Obj) (ObjResp, error) {
|
ret, _ := utils.SliceConvert(objs, func(src model.Obj) (ObjResp, error) {
|
||||||
return toObjsRespWithoutSignAndThumb(src), nil
|
return toObjsRespWithoutSignAndThumb(src), nil
|
||||||
})
|
})
|
||||||
common.SuccessResp(c, ArchiveListResp{
|
common.SuccessResp(c, common.PageResp{
|
||||||
Content: ret,
|
Content: ret,
|
||||||
Total: int64(total),
|
Total: int64(total),
|
||||||
})
|
})
|
||||||
@ -298,7 +320,7 @@ func ArchiveDown(c *gin.Context) {
|
|||||||
filename := stdpath.Base(innerPath)
|
filename := stdpath.Base(innerPath)
|
||||||
storage, err := fs.GetStorage(archiveRawPath, &fs.GetStoragesArgs{})
|
storage, err := fs.GetStorage(archiveRawPath, &fs.GetStoragesArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorPage(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if common.ShouldProxy(storage, filename) {
|
if common.ShouldProxy(storage, filename) {
|
||||||
@ -318,7 +340,7 @@ func ArchiveDown(c *gin.Context) {
|
|||||||
InnerPath: innerPath,
|
InnerPath: innerPath,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorPage(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
redirect(c, link)
|
redirect(c, link)
|
||||||
@ -332,7 +354,7 @@ func ArchiveProxy(c *gin.Context) {
|
|||||||
filename := stdpath.Base(innerPath)
|
filename := stdpath.Base(innerPath)
|
||||||
storage, err := fs.GetStorage(archiveRawPath, &fs.GetStoragesArgs{})
|
storage, err := fs.GetStorage(archiveRawPath, &fs.GetStoragesArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorPage(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if canProxy(storage, filename) {
|
if canProxy(storage, filename) {
|
||||||
@ -348,16 +370,34 @@ func ArchiveProxy(c *gin.Context) {
|
|||||||
InnerPath: innerPath,
|
InnerPath: innerPath,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorPage(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
proxy(c, link, file, storage.GetStorage().ProxyRange)
|
proxy(c, link, file, storage.GetStorage().ProxyRange)
|
||||||
} else {
|
} else {
|
||||||
common.ErrorStrResp(c, "proxy not allowed", 403)
|
common.ErrorPage(c, errors.New("proxy not allowed"), 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func proxyInternalExtract(c *gin.Context, rc io.ReadCloser, size int64, fileName string) {
|
||||||
|
defer func() {
|
||||||
|
if err := rc.Close(); err != nil {
|
||||||
|
log.Errorf("failed to close file streamer, %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
headers := map[string]string{
|
||||||
|
"Referrer-Policy": "no-referrer",
|
||||||
|
"Cache-Control": "max-age=0, no-cache, no-store, must-revalidate",
|
||||||
|
}
|
||||||
|
headers["Content-Disposition"] = utils.GenerateContentDisposition(fileName)
|
||||||
|
contentType := c.Request.Header.Get("Content-Type")
|
||||||
|
if contentType == "" {
|
||||||
|
contentType = utils.GetMimeType(fileName)
|
||||||
|
}
|
||||||
|
c.DataFromReader(200, size, contentType, rc, headers)
|
||||||
|
}
|
||||||
|
|
||||||
func ArchiveInternalExtract(c *gin.Context) {
|
func ArchiveInternalExtract(c *gin.Context) {
|
||||||
archiveRawPath := c.Request.Context().Value(conf.PathKey).(string)
|
archiveRawPath := c.Request.Context().Value(conf.PathKey).(string)
|
||||||
innerPath := utils.FixAndCleanPath(c.Query("inner"))
|
innerPath := utils.FixAndCleanPath(c.Query("inner"))
|
||||||
@ -373,25 +413,11 @@ func ArchiveInternalExtract(c *gin.Context) {
|
|||||||
InnerPath: innerPath,
|
InnerPath: innerPath,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorPage(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
if err := rc.Close(); err != nil {
|
|
||||||
log.Errorf("failed to close file streamer, %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
headers := map[string]string{
|
|
||||||
"Referrer-Policy": "no-referrer",
|
|
||||||
"Cache-Control": "max-age=0, no-cache, no-store, must-revalidate",
|
|
||||||
}
|
|
||||||
fileName := stdpath.Base(innerPath)
|
fileName := stdpath.Base(innerPath)
|
||||||
headers["Content-Disposition"] = utils.GenerateContentDisposition(fileName)
|
proxyInternalExtract(c, rc, size, fileName)
|
||||||
contentType := c.Request.Header.Get("Content-Type")
|
|
||||||
if contentType == "" {
|
|
||||||
contentType = utils.GetMimeType(fileName)
|
|
||||||
}
|
|
||||||
c.DataFromReader(200, size, contentType, rc, headers)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ArchiveExtensions(c *gin.Context) {
|
func ArchiveExtensions(c *gin.Context) {
|
||||||
|
@ -26,7 +26,7 @@ func Down(c *gin.Context) {
|
|||||||
filename := stdpath.Base(rawPath)
|
filename := stdpath.Base(rawPath)
|
||||||
storage, err := fs.GetStorage(rawPath, &fs.GetStoragesArgs{})
|
storage, err := fs.GetStorage(rawPath, &fs.GetStoragesArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorPage(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if common.ShouldProxy(storage, filename) {
|
if common.ShouldProxy(storage, filename) {
|
||||||
@ -40,7 +40,7 @@ func Down(c *gin.Context) {
|
|||||||
Redirect: true,
|
Redirect: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorPage(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
redirect(c, link)
|
redirect(c, link)
|
||||||
@ -52,7 +52,7 @@ func Proxy(c *gin.Context) {
|
|||||||
filename := stdpath.Base(rawPath)
|
filename := stdpath.Base(rawPath)
|
||||||
storage, err := fs.GetStorage(rawPath, &fs.GetStoragesArgs{})
|
storage, err := fs.GetStorage(rawPath, &fs.GetStoragesArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorPage(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if canProxy(storage, filename) {
|
if canProxy(storage, filename) {
|
||||||
@ -67,12 +67,12 @@ func Proxy(c *gin.Context) {
|
|||||||
Type: c.Query("type"),
|
Type: c.Query("type"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorPage(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
proxy(c, link, file, storage.GetStorage().ProxyRange)
|
proxy(c, link, file, storage.GetStorage().ProxyRange)
|
||||||
} else {
|
} else {
|
||||||
common.ErrorStrResp(c, "proxy not allowed", 403)
|
common.ErrorPage(c, errors.New("proxy not allowed"), 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,7 +89,7 @@ func redirect(c *gin.Context, link *model.Link) {
|
|||||||
}
|
}
|
||||||
link.URL, err = utils.InjectQuery(link.URL, query)
|
link.URL, err = utils.InjectQuery(link.URL, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorPage(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,7 +106,7 @@ func proxy(c *gin.Context, link *model.Link, file model.Obj, proxyRange bool) {
|
|||||||
}
|
}
|
||||||
link.URL, err = utils.InjectQuery(link.URL, query)
|
link.URL, err = utils.InjectQuery(link.URL, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorPage(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,9 +149,9 @@ func proxy(c *gin.Context, link *model.Link, file model.Obj, proxyRange bool) {
|
|||||||
log.Errorf("%s %s local proxy error: %+v", c.Request.Method, c.Request.URL.Path, err)
|
log.Errorf("%s %s local proxy error: %+v", c.Request.Method, c.Request.URL.Path, err)
|
||||||
} else {
|
} else {
|
||||||
if statusCode, ok := errors.Unwrap(err).(net.ErrorHttpStatusCode); ok {
|
if statusCode, ok := errors.Unwrap(err).(net.ErrorHttpStatusCode); ok {
|
||||||
common.ErrorResp(c, err, int(statusCode), true)
|
common.ErrorPage(c, err, int(statusCode), true)
|
||||||
} else {
|
} else {
|
||||||
common.ErrorResp(c, err, 500, true)
|
common.ErrorPage(c, err, 500, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,14 +56,27 @@ type FsListResp struct {
|
|||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func FsList(c *gin.Context) {
|
func FsListSplit(c *gin.Context) {
|
||||||
var req ListReq
|
var req ListReq
|
||||||
if err := c.ShouldBind(&req); err != nil {
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
common.ErrorResp(c, err, 400)
|
common.ErrorResp(c, err, 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req.Validate()
|
req.Validate()
|
||||||
|
if strings.HasPrefix(req.Path, "/@s") {
|
||||||
|
req.Path = strings.TrimPrefix(req.Path, "/@s")
|
||||||
|
SharingList(c, &req)
|
||||||
|
return
|
||||||
|
}
|
||||||
user := c.Request.Context().Value(conf.UserKey).(*model.User)
|
user := c.Request.Context().Value(conf.UserKey).(*model.User)
|
||||||
|
if user.IsGuest() && user.Disabled {
|
||||||
|
common.ErrorStrResp(c, "Guest user is disabled, login please", 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
FsList(c, &req, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FsList(c *gin.Context, req *ListReq, user *model.User) {
|
||||||
reqPath, err := user.JoinPath(req.Path)
|
reqPath, err := user.JoinPath(req.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
@ -243,13 +256,26 @@ type FsGetResp struct {
|
|||||||
Related []ObjResp `json:"related"`
|
Related []ObjResp `json:"related"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func FsGet(c *gin.Context) {
|
func FsGetSplit(c *gin.Context) {
|
||||||
var req FsGetReq
|
var req FsGetReq
|
||||||
if err := c.ShouldBind(&req); err != nil {
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
common.ErrorResp(c, err, 400)
|
common.ErrorResp(c, err, 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(req.Path, "/@s") {
|
||||||
|
req.Path = strings.TrimPrefix(req.Path, "/@s")
|
||||||
|
SharingGet(c, &req)
|
||||||
|
return
|
||||||
|
}
|
||||||
user := c.Request.Context().Value(conf.UserKey).(*model.User)
|
user := c.Request.Context().Value(conf.UserKey).(*model.User)
|
||||||
|
if user.IsGuest() && user.Disabled {
|
||||||
|
common.ErrorStrResp(c, "Guest user is disabled, login please", 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
FsGet(c, &req, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FsGet(c *gin.Context, req *FsGetReq, user *model.User) {
|
||||||
reqPath, err := user.JoinPath(req.Path)
|
reqPath, err := user.JoinPath(req.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
|
568
server/handles/sharing.go
Normal file
568
server/handles/sharing.go
Normal file
@ -0,0 +1,568 @@
|
|||||||
|
package handles
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
stdpath "path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/setting"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/sharing"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/server/common"
|
||||||
|
"github.com/OpenListTeam/go-cache"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SharingGet(c *gin.Context, req *FsGetReq) {
|
||||||
|
sid, path, _ := strings.Cut(strings.TrimPrefix(req.Path, "/"), "/")
|
||||||
|
if sid == "" {
|
||||||
|
common.ErrorStrResp(c, "invalid share id", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s, obj, err := sharing.Get(c.Request.Context(), sid, path, model.SharingListArgs{
|
||||||
|
Refresh: false,
|
||||||
|
Pwd: req.Password,
|
||||||
|
})
|
||||||
|
if dealError(c, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = countAccess(c.ClientIP(), s)
|
||||||
|
fakePath := fmt.Sprintf("/%s/%s", sid, path)
|
||||||
|
url := ""
|
||||||
|
if !obj.IsDir() {
|
||||||
|
url = fmt.Sprintf("%s/sd%s", common.GetApiUrl(c), utils.EncodePath(fakePath, true))
|
||||||
|
if s.Pwd != "" {
|
||||||
|
url += "?pwd=" + s.Pwd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thumb, _ := model.GetThumb(obj)
|
||||||
|
common.SuccessResp(c, FsGetResp{
|
||||||
|
ObjResp: ObjResp{
|
||||||
|
Id: "",
|
||||||
|
Path: fakePath,
|
||||||
|
Name: obj.GetName(),
|
||||||
|
Size: obj.GetSize(),
|
||||||
|
IsDir: obj.IsDir(),
|
||||||
|
Modified: obj.ModTime(),
|
||||||
|
Created: obj.CreateTime(),
|
||||||
|
HashInfoStr: obj.GetHash().String(),
|
||||||
|
HashInfo: obj.GetHash().Export(),
|
||||||
|
Sign: "",
|
||||||
|
Type: utils.GetFileType(obj.GetName()),
|
||||||
|
Thumb: thumb,
|
||||||
|
},
|
||||||
|
RawURL: url,
|
||||||
|
Readme: s.Readme,
|
||||||
|
Header: s.Header,
|
||||||
|
Provider: "unknown",
|
||||||
|
Related: nil,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func SharingList(c *gin.Context, req *ListReq) {
|
||||||
|
sid, path, _ := strings.Cut(strings.TrimPrefix(req.Path, "/"), "/")
|
||||||
|
if sid == "" {
|
||||||
|
common.ErrorStrResp(c, "invalid share id", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s, objs, err := sharing.List(c.Request.Context(), sid, path, model.SharingListArgs{
|
||||||
|
Refresh: req.Refresh,
|
||||||
|
Pwd: req.Password,
|
||||||
|
})
|
||||||
|
if dealError(c, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = countAccess(c.ClientIP(), s)
|
||||||
|
fakePath := fmt.Sprintf("/%s/%s", sid, path)
|
||||||
|
total, objs := pagination(objs, &req.PageReq)
|
||||||
|
common.SuccessResp(c, FsListResp{
|
||||||
|
Content: utils.MustSliceConvert(objs, func(obj model.Obj) ObjResp {
|
||||||
|
thumb, _ := model.GetThumb(obj)
|
||||||
|
return ObjResp{
|
||||||
|
Id: "",
|
||||||
|
Path: stdpath.Join(fakePath, obj.GetName()),
|
||||||
|
Name: obj.GetName(),
|
||||||
|
Size: obj.GetSize(),
|
||||||
|
IsDir: obj.IsDir(),
|
||||||
|
Modified: obj.ModTime(),
|
||||||
|
Created: obj.CreateTime(),
|
||||||
|
HashInfoStr: obj.GetHash().String(),
|
||||||
|
HashInfo: obj.GetHash().Export(),
|
||||||
|
Sign: "",
|
||||||
|
Thumb: thumb,
|
||||||
|
Type: utils.GetObjType(obj.GetName(), obj.IsDir()),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Total: int64(total),
|
||||||
|
Readme: s.Readme,
|
||||||
|
Header: s.Header,
|
||||||
|
Write: false,
|
||||||
|
Provider: "unknown",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func SharingArchiveMeta(c *gin.Context, req *ArchiveMetaReq) {
|
||||||
|
if !setting.GetBool(conf.ShareArchivePreview) {
|
||||||
|
common.ErrorStrResp(c, "sharing archives previewing is not allowed", 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sid, path, _ := strings.Cut(strings.TrimPrefix(req.Path, "/"), "/")
|
||||||
|
if sid == "" {
|
||||||
|
common.ErrorStrResp(c, "invalid share id", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
archiveArgs := model.ArchiveArgs{
|
||||||
|
LinkArgs: model.LinkArgs{
|
||||||
|
Header: c.Request.Header,
|
||||||
|
Type: c.Query("type"),
|
||||||
|
},
|
||||||
|
Password: req.ArchivePass,
|
||||||
|
}
|
||||||
|
s, ret, err := sharing.ArchiveMeta(c.Request.Context(), sid, path, model.SharingArchiveMetaArgs{
|
||||||
|
ArchiveMetaArgs: model.ArchiveMetaArgs{
|
||||||
|
ArchiveArgs: archiveArgs,
|
||||||
|
Refresh: req.Refresh,
|
||||||
|
},
|
||||||
|
Pwd: req.Password,
|
||||||
|
})
|
||||||
|
if dealError(c, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = countAccess(c.ClientIP(), s)
|
||||||
|
fakePath := fmt.Sprintf("/%s/%s", sid, path)
|
||||||
|
url := fmt.Sprintf("%s/sad%s", common.GetApiUrl(c), utils.EncodePath(fakePath, true))
|
||||||
|
if s.Pwd != "" {
|
||||||
|
url += "?pwd=" + s.Pwd
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, ArchiveMetaResp{
|
||||||
|
Comment: ret.GetComment(),
|
||||||
|
IsEncrypted: ret.IsEncrypted(),
|
||||||
|
Content: toContentResp(ret.GetTree()),
|
||||||
|
Sort: ret.Sort,
|
||||||
|
RawURL: url,
|
||||||
|
Sign: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func SharingArchiveList(c *gin.Context, req *ArchiveListReq) {
|
||||||
|
if !setting.GetBool(conf.ShareArchivePreview) {
|
||||||
|
common.ErrorStrResp(c, "sharing archives previewing is not allowed", 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sid, path, _ := strings.Cut(strings.TrimPrefix(req.Path, "/"), "/")
|
||||||
|
if sid == "" {
|
||||||
|
common.ErrorStrResp(c, "invalid share id", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
innerArgs := model.ArchiveInnerArgs{
|
||||||
|
ArchiveArgs: model.ArchiveArgs{
|
||||||
|
LinkArgs: model.LinkArgs{
|
||||||
|
Header: c.Request.Header,
|
||||||
|
Type: c.Query("type"),
|
||||||
|
},
|
||||||
|
Password: req.ArchivePass,
|
||||||
|
},
|
||||||
|
InnerPath: utils.FixAndCleanPath(req.InnerPath),
|
||||||
|
}
|
||||||
|
s, objs, err := sharing.ArchiveList(c.Request.Context(), sid, path, model.SharingArchiveListArgs{
|
||||||
|
ArchiveListArgs: model.ArchiveListArgs{
|
||||||
|
ArchiveInnerArgs: innerArgs,
|
||||||
|
Refresh: req.Refresh,
|
||||||
|
},
|
||||||
|
Pwd: req.Password,
|
||||||
|
})
|
||||||
|
if dealError(c, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = countAccess(c.ClientIP(), s)
|
||||||
|
total, objs := pagination(objs, &req.PageReq)
|
||||||
|
ret, _ := utils.SliceConvert(objs, func(src model.Obj) (ObjResp, error) {
|
||||||
|
return toObjsRespWithoutSignAndThumb(src), nil
|
||||||
|
})
|
||||||
|
common.SuccessResp(c, common.PageResp{
|
||||||
|
Content: ret,
|
||||||
|
Total: int64(total),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func SharingDown(c *gin.Context) {
|
||||||
|
sid := c.Request.Context().Value(conf.SharingIDKey).(string)
|
||||||
|
path := c.Request.Context().Value(conf.PathKey).(string)
|
||||||
|
pwd := c.Query("pwd")
|
||||||
|
s, err := op.GetSharingById(sid)
|
||||||
|
if err == nil {
|
||||||
|
if !s.Valid() {
|
||||||
|
err = errs.InvalidSharing
|
||||||
|
} else if !s.Verify(pwd) {
|
||||||
|
err = errs.WrongShareCode
|
||||||
|
} else if len(s.Files) != 1 && path == "/" {
|
||||||
|
err = errors.New("cannot get sharing root link")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dealErrorPage(c, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
unwrapPath, err := op.GetSharingUnwrapPath(s, path)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorPage(c, errors.New("failed get sharing unwrap path"), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
storage, actualPath, err := op.GetStorageAndActualPath(unwrapPath)
|
||||||
|
if dealErrorPage(c, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if setting.GetBool(conf.ShareForceProxy) || common.ShouldProxy(storage, stdpath.Base(actualPath)) {
|
||||||
|
link, obj, err := op.Link(c.Request.Context(), storage, actualPath, model.LinkArgs{
|
||||||
|
Header: c.Request.Header,
|
||||||
|
Type: c.Query("type"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorPage(c, errors.WithMessage(err, "failed get sharing link"), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = countAccess(c.ClientIP(), s)
|
||||||
|
proxy(c, link, obj, storage.GetStorage().ProxyRange)
|
||||||
|
} else {
|
||||||
|
link, _, err := op.Link(c.Request.Context(), storage, actualPath, model.LinkArgs{
|
||||||
|
IP: c.ClientIP(),
|
||||||
|
Header: c.Request.Header,
|
||||||
|
Type: c.Query("type"),
|
||||||
|
Redirect: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorPage(c, errors.WithMessage(err, "failed get sharing link"), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = countAccess(c.ClientIP(), s)
|
||||||
|
redirect(c, link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SharingArchiveExtract(c *gin.Context) {
|
||||||
|
if !setting.GetBool(conf.ShareArchivePreview) {
|
||||||
|
common.ErrorPage(c, errors.New("sharing archives previewing is not allowed"), 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sid := c.Request.Context().Value(conf.SharingIDKey).(string)
|
||||||
|
path := c.Request.Context().Value(conf.PathKey).(string)
|
||||||
|
pwd := c.Query("pwd")
|
||||||
|
innerPath := utils.FixAndCleanPath(c.Query("inner"))
|
||||||
|
archivePass := c.Query("pass")
|
||||||
|
s, err := op.GetSharingById(sid)
|
||||||
|
if err == nil {
|
||||||
|
if !s.Valid() {
|
||||||
|
err = errs.InvalidSharing
|
||||||
|
} else if !s.Verify(pwd) {
|
||||||
|
err = errs.WrongShareCode
|
||||||
|
} else if len(s.Files) != 1 && path == "/" {
|
||||||
|
err = errors.New("cannot extract sharing root")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dealErrorPage(c, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
unwrapPath, err := op.GetSharingUnwrapPath(s, path)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorPage(c, errors.New("failed get sharing unwrap path"), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
storage, actualPath, err := op.GetStorageAndActualPath(unwrapPath)
|
||||||
|
if dealErrorPage(c, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
args := model.ArchiveInnerArgs{
|
||||||
|
ArchiveArgs: model.ArchiveArgs{
|
||||||
|
LinkArgs: model.LinkArgs{
|
||||||
|
Header: c.Request.Header,
|
||||||
|
Type: c.Query("type"),
|
||||||
|
},
|
||||||
|
Password: archivePass,
|
||||||
|
},
|
||||||
|
InnerPath: innerPath,
|
||||||
|
}
|
||||||
|
if _, ok := storage.(driver.ArchiveReader); ok {
|
||||||
|
if setting.GetBool(conf.ShareForceProxy) || common.ShouldProxy(storage, stdpath.Base(actualPath)) {
|
||||||
|
link, obj, err := op.DriverExtract(c.Request.Context(), storage, actualPath, args)
|
||||||
|
if dealErrorPage(c, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
proxy(c, link, obj, storage.GetStorage().ProxyRange)
|
||||||
|
} else {
|
||||||
|
args.Redirect = true
|
||||||
|
link, _, err := op.DriverExtract(c.Request.Context(), storage, actualPath, args)
|
||||||
|
if dealErrorPage(c, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
redirect(c, link)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rc, size, err := op.InternalExtract(c.Request.Context(), storage, actualPath, args)
|
||||||
|
if dealErrorPage(c, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fileName := stdpath.Base(innerPath)
|
||||||
|
proxyInternalExtract(c, rc, size, fileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dealError(c *gin.Context, err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
} else if errors.Is(err, errs.SharingNotFound) {
|
||||||
|
common.ErrorStrResp(c, "the share does not exist", 500)
|
||||||
|
} else if errors.Is(err, errs.InvalidSharing) {
|
||||||
|
common.ErrorStrResp(c, "the share has expired or is no longer valid", 500)
|
||||||
|
} else if errors.Is(err, errs.WrongShareCode) {
|
||||||
|
common.ErrorResp(c, err, 403)
|
||||||
|
} else if errors.Is(err, errs.WrongArchivePassword) {
|
||||||
|
common.ErrorResp(c, err, 202)
|
||||||
|
} else {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func dealErrorPage(c *gin.Context, err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
} else if errors.Is(err, errs.SharingNotFound) {
|
||||||
|
common.ErrorPage(c, errors.New("the share does not exist"), 500)
|
||||||
|
} else if errors.Is(err, errs.InvalidSharing) {
|
||||||
|
common.ErrorPage(c, errors.New("the share has expired or is no longer valid"), 500)
|
||||||
|
} else if errors.Is(err, errs.WrongShareCode) {
|
||||||
|
common.ErrorPage(c, err, 403)
|
||||||
|
} else if errors.Is(err, errs.WrongArchivePassword) {
|
||||||
|
common.ErrorPage(c, err, 202)
|
||||||
|
} else {
|
||||||
|
common.ErrorPage(c, err, 500)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type SharingResp struct {
|
||||||
|
*model.Sharing
|
||||||
|
CreatorName string `json:"creator"`
|
||||||
|
CreatorRole int `json:"creator_role"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSharing(c *gin.Context) {
|
||||||
|
sid := c.Query("id")
|
||||||
|
user := c.Request.Context().Value(conf.UserKey).(*model.User)
|
||||||
|
s, err := op.GetSharingById(sid)
|
||||||
|
if err != nil || (!user.IsAdmin() && s.Creator.ID != user.ID) {
|
||||||
|
common.ErrorStrResp(c, "sharing not found", 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, SharingResp{
|
||||||
|
Sharing: s,
|
||||||
|
CreatorName: s.Creator.Username,
|
||||||
|
CreatorRole: s.Creator.Role,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListSharings(c *gin.Context) {
|
||||||
|
var req model.PageReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Validate()
|
||||||
|
user := c.Request.Context().Value(conf.UserKey).(*model.User)
|
||||||
|
var sharings []model.Sharing
|
||||||
|
var total int64
|
||||||
|
var err error
|
||||||
|
if user.IsAdmin() {
|
||||||
|
sharings, total, err = op.GetSharings(req.Page, req.PerPage)
|
||||||
|
} else {
|
||||||
|
sharings, total, err = op.GetSharingsByCreatorId(user.ID, req.Page, req.PerPage)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, common.PageResp{
|
||||||
|
Content: utils.MustSliceConvert(sharings, func(s model.Sharing) SharingResp {
|
||||||
|
return SharingResp{
|
||||||
|
Sharing: &s,
|
||||||
|
CreatorName: s.Creator.Username,
|
||||||
|
CreatorRole: s.Creator.Role,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Total: total,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateSharingReq struct {
|
||||||
|
Files []string `json:"files"`
|
||||||
|
Expires *time.Time `json:"expires"`
|
||||||
|
Pwd string `json:"pwd"`
|
||||||
|
MaxAccessed int `json:"max_accessed"`
|
||||||
|
Disabled bool `json:"disabled"`
|
||||||
|
Remark string `json:"remark"`
|
||||||
|
Readme string `json:"readme"`
|
||||||
|
Header string `json:"header"`
|
||||||
|
model.Sort
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateSharingReq struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Accessed int `json:"accessed"`
|
||||||
|
CreateSharingReq
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateSharing(c *gin.Context) {
|
||||||
|
var req UpdateSharingReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(req.Files) == 0 || (len(req.Files) == 1 && req.Files[0] == "") {
|
||||||
|
common.ErrorStrResp(c, "must add at least 1 object", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := c.Request.Context().Value(conf.UserKey).(*model.User)
|
||||||
|
if !user.CanShare() {
|
||||||
|
common.ErrorStrResp(c, "permission denied", 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i, s := range req.Files {
|
||||||
|
s = utils.FixAndCleanPath(s)
|
||||||
|
req.Files[i] = s
|
||||||
|
if !user.IsAdmin() && !strings.HasPrefix(s, user.BasePath) {
|
||||||
|
common.ErrorStrResp(c, fmt.Sprintf("permission denied to share path [%s]", s), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s, err := op.GetSharingById(req.ID)
|
||||||
|
if err != nil || (!user.IsAdmin() && s.CreatorId != user.ID) {
|
||||||
|
common.ErrorStrResp(c, "sharing not found", 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.Files = req.Files
|
||||||
|
s.Expires = req.Expires
|
||||||
|
s.Pwd = req.Pwd
|
||||||
|
s.Accessed = req.Accessed
|
||||||
|
s.MaxAccessed = req.MaxAccessed
|
||||||
|
s.Disabled = req.Disabled
|
||||||
|
s.Sort = req.Sort
|
||||||
|
s.Header = req.Header
|
||||||
|
s.Readme = req.Readme
|
||||||
|
s.Remark = req.Remark
|
||||||
|
if err = op.UpdateSharing(s); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
} else {
|
||||||
|
common.SuccessResp(c, SharingResp{
|
||||||
|
Sharing: s,
|
||||||
|
CreatorName: s.Creator.Username,
|
||||||
|
CreatorRole: s.Creator.Role,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateSharing(c *gin.Context) {
|
||||||
|
var req CreateSharingReq
|
||||||
|
var err error
|
||||||
|
if err = c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(req.Files) == 0 || (len(req.Files) == 1 && req.Files[0] == "") {
|
||||||
|
common.ErrorStrResp(c, "must add at least 1 object", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := c.Request.Context().Value(conf.UserKey).(*model.User)
|
||||||
|
if !user.CanShare() {
|
||||||
|
common.ErrorStrResp(c, "permission denied", 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i, s := range req.Files {
|
||||||
|
s = utils.FixAndCleanPath(s)
|
||||||
|
req.Files[i] = s
|
||||||
|
if !user.IsAdmin() && !strings.HasPrefix(s, user.BasePath) {
|
||||||
|
common.ErrorStrResp(c, fmt.Sprintf("permission denied to share path [%s]", s), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s := &model.Sharing{
|
||||||
|
SharingDB: &model.SharingDB{
|
||||||
|
Expires: req.Expires,
|
||||||
|
Pwd: req.Pwd,
|
||||||
|
Accessed: 0,
|
||||||
|
MaxAccessed: req.MaxAccessed,
|
||||||
|
Disabled: req.Disabled,
|
||||||
|
Sort: req.Sort,
|
||||||
|
Remark: req.Remark,
|
||||||
|
Readme: req.Readme,
|
||||||
|
Header: req.Header,
|
||||||
|
},
|
||||||
|
Files: req.Files,
|
||||||
|
Creator: user,
|
||||||
|
}
|
||||||
|
var id string
|
||||||
|
if id, err = op.CreateSharing(s); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
} else {
|
||||||
|
s.ID = id
|
||||||
|
common.SuccessResp(c, SharingResp{
|
||||||
|
Sharing: s,
|
||||||
|
CreatorName: s.Creator.Username,
|
||||||
|
CreatorRole: s.Creator.Role,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteSharing(c *gin.Context) {
|
||||||
|
sid := c.Query("id")
|
||||||
|
user := c.Request.Context().Value(conf.UserKey).(*model.User)
|
||||||
|
s, err := op.GetSharingById(sid)
|
||||||
|
if err != nil || (!user.IsAdmin() && s.CreatorId != user.ID) {
|
||||||
|
common.ErrorResp(c, err, 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = op.DeleteSharing(sid); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
} else {
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetEnableSharing(disable bool) func(ctx *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
sid := c.Query("id")
|
||||||
|
user := c.Request.Context().Value(conf.UserKey).(*model.User)
|
||||||
|
s, err := op.GetSharingById(sid)
|
||||||
|
if err != nil || (!user.IsAdmin() && s.CreatorId != user.ID) {
|
||||||
|
common.ErrorStrResp(c, "sharing not found", 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.Disabled = disable
|
||||||
|
if err = op.UpdateSharing(s, true); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
} else {
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
AccessCache = cache.NewMemCache[interface{}]()
|
||||||
|
AccessCountDelay = 30 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
func countAccess(ip string, s *model.Sharing) error {
|
||||||
|
key := fmt.Sprintf("%s:%s", s.ID, ip)
|
||||||
|
_, ok := AccessCache.Get(key)
|
||||||
|
if !ok {
|
||||||
|
AccessCache.Set(key, struct{}{}, cache.WithEx[interface{}](AccessCountDelay))
|
||||||
|
s.Accessed += 1
|
||||||
|
return op.UpdateSharing(s, true)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -14,7 +14,8 @@ import (
|
|||||||
|
|
||||||
// Auth is a middleware that checks if the user is logged in.
|
// Auth is a middleware that checks if the user is logged in.
|
||||||
// if token is empty, set user to guest
|
// if token is empty, set user to guest
|
||||||
func Auth(c *gin.Context) {
|
func Auth(allowDisabledGuest bool) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
token := c.GetHeader("Authorization")
|
token := c.GetHeader("Authorization")
|
||||||
if subtle.ConstantTimeCompare([]byte(token), []byte(setting.GetStr(conf.Token))) == 1 {
|
if subtle.ConstantTimeCompare([]byte(token), []byte(setting.GetStr(conf.Token))) == 1 {
|
||||||
admin, err := op.GetAdmin()
|
admin, err := op.GetAdmin()
|
||||||
@ -35,7 +36,7 @@ func Auth(c *gin.Context) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if guest.Disabled {
|
if !allowDisabledGuest && guest.Disabled {
|
||||||
common.ErrorStrResp(c, "Guest user is disabled, login please", 401)
|
common.ErrorStrResp(c, "Guest user is disabled, login please", 401)
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@ -72,6 +73,7 @@ func Auth(c *gin.Context) {
|
|||||||
log.Debugf("use login token: %+v", user)
|
log.Debugf("use login token: %+v", user)
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Authn(c *gin.Context) {
|
func Authn(c *gin.Context) {
|
||||||
token := c.GetHeader("Authorization")
|
token := c.GetHeader("Authorization")
|
||||||
|
@ -15,14 +15,19 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Down(verifyFunc func(string, string) error) func(c *gin.Context) {
|
func PathParse(c *gin.Context) {
|
||||||
return func(c *gin.Context) {
|
|
||||||
rawPath := parsePath(c.Param("path"))
|
rawPath := parsePath(c.Param("path"))
|
||||||
common.GinWithValue(c, conf.PathKey, rawPath)
|
common.GinWithValue(c, conf.PathKey, rawPath)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Down(verifyFunc func(string, string) error) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
rawPath := c.Request.Context().Value(conf.PathKey).(string)
|
||||||
meta, err := op.GetNearestMeta(rawPath)
|
meta, err := op.GetNearestMeta(rawPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||||
common.ErrorResp(c, err, 500, true)
|
common.ErrorPage(c, err, 500, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -32,7 +37,7 @@ func Down(verifyFunc func(string, string) error) func(c *gin.Context) {
|
|||||||
s := c.Query("sign")
|
s := c.Query("sign")
|
||||||
err = verifyFunc(rawPath, strings.TrimSuffix(s, "/"))
|
err = verifyFunc(rawPath, strings.TrimSuffix(s, "/"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 401)
|
common.ErrorPage(c, err, 401)
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
18
server/middlewares/sharing.go
Normal file
18
server/middlewares/sharing.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/server/common"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SharingIdParse(c *gin.Context) {
|
||||||
|
sid := c.Param("sid")
|
||||||
|
common.GinWithValue(c, conf.SharingIDKey, sid)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func EmptyPathParse(c *gin.Context) {
|
||||||
|
common.GinWithValue(c, conf.PathKey, "/")
|
||||||
|
c.Next()
|
||||||
|
}
|
@ -44,20 +44,29 @@ func Init(e *gin.Engine) {
|
|||||||
|
|
||||||
downloadLimiter := middlewares.DownloadRateLimiter(stream.ClientDownloadLimit)
|
downloadLimiter := middlewares.DownloadRateLimiter(stream.ClientDownloadLimit)
|
||||||
signCheck := middlewares.Down(sign.Verify)
|
signCheck := middlewares.Down(sign.Verify)
|
||||||
g.GET("/d/*path", signCheck, downloadLimiter, handles.Down)
|
g.GET("/d/*path", middlewares.PathParse, signCheck, downloadLimiter, handles.Down)
|
||||||
g.GET("/p/*path", signCheck, downloadLimiter, handles.Proxy)
|
g.GET("/p/*path", middlewares.PathParse, signCheck, downloadLimiter, handles.Proxy)
|
||||||
g.HEAD("/d/*path", signCheck, handles.Down)
|
g.HEAD("/d/*path", middlewares.PathParse, signCheck, handles.Down)
|
||||||
g.HEAD("/p/*path", signCheck, handles.Proxy)
|
g.HEAD("/p/*path", middlewares.PathParse, signCheck, handles.Proxy)
|
||||||
archiveSignCheck := middlewares.Down(sign.VerifyArchive)
|
archiveSignCheck := middlewares.Down(sign.VerifyArchive)
|
||||||
g.GET("/ad/*path", archiveSignCheck, downloadLimiter, handles.ArchiveDown)
|
g.GET("/ad/*path", middlewares.PathParse, archiveSignCheck, downloadLimiter, handles.ArchiveDown)
|
||||||
g.GET("/ap/*path", archiveSignCheck, downloadLimiter, handles.ArchiveProxy)
|
g.GET("/ap/*path", middlewares.PathParse, archiveSignCheck, downloadLimiter, handles.ArchiveProxy)
|
||||||
g.GET("/ae/*path", archiveSignCheck, downloadLimiter, handles.ArchiveInternalExtract)
|
g.GET("/ae/*path", middlewares.PathParse, archiveSignCheck, downloadLimiter, handles.ArchiveInternalExtract)
|
||||||
g.HEAD("/ad/*path", archiveSignCheck, handles.ArchiveDown)
|
g.HEAD("/ad/*path", middlewares.PathParse, archiveSignCheck, handles.ArchiveDown)
|
||||||
g.HEAD("/ap/*path", archiveSignCheck, handles.ArchiveProxy)
|
g.HEAD("/ap/*path", middlewares.PathParse, archiveSignCheck, handles.ArchiveProxy)
|
||||||
g.HEAD("/ae/*path", archiveSignCheck, handles.ArchiveInternalExtract)
|
g.HEAD("/ae/*path", middlewares.PathParse, archiveSignCheck, handles.ArchiveInternalExtract)
|
||||||
|
|
||||||
|
g.GET("/sd/:sid", middlewares.EmptyPathParse, middlewares.SharingIdParse, downloadLimiter, handles.SharingDown)
|
||||||
|
g.GET("/sd/:sid/*path", middlewares.PathParse, middlewares.SharingIdParse, downloadLimiter, handles.SharingDown)
|
||||||
|
g.HEAD("/sd/:sid", middlewares.EmptyPathParse, middlewares.SharingIdParse, handles.SharingDown)
|
||||||
|
g.HEAD("/sd/:sid/*path", middlewares.PathParse, middlewares.SharingIdParse, handles.SharingDown)
|
||||||
|
g.GET("/sad/:sid", middlewares.EmptyPathParse, middlewares.SharingIdParse, downloadLimiter, handles.SharingArchiveExtract)
|
||||||
|
g.GET("/sad/:sid/*path", middlewares.PathParse, middlewares.SharingIdParse, downloadLimiter, handles.SharingArchiveExtract)
|
||||||
|
g.HEAD("/sad/:sid", middlewares.EmptyPathParse, middlewares.SharingIdParse, handles.SharingArchiveExtract)
|
||||||
|
g.HEAD("/sad/:sid/*path", middlewares.PathParse, middlewares.SharingIdParse, handles.SharingArchiveExtract)
|
||||||
|
|
||||||
api := g.Group("/api")
|
api := g.Group("/api")
|
||||||
auth := api.Group("", middlewares.Auth)
|
auth := api.Group("", middlewares.Auth(false))
|
||||||
webauthn := api.Group("/authn", middlewares.Authn)
|
webauthn := api.Group("/authn", middlewares.Authn)
|
||||||
|
|
||||||
api.POST("/auth/login", handles.Login)
|
api.POST("/auth/login", handles.Login)
|
||||||
@ -93,7 +102,9 @@ func Init(e *gin.Engine) {
|
|||||||
public.Any("/archive_extensions", handles.ArchiveExtensions)
|
public.Any("/archive_extensions", handles.ArchiveExtensions)
|
||||||
|
|
||||||
_fs(auth.Group("/fs"))
|
_fs(auth.Group("/fs"))
|
||||||
|
fsAndShare(api.Group("/fs", middlewares.Auth(true)))
|
||||||
_task(auth.Group("/task", middlewares.AuthNotGuest))
|
_task(auth.Group("/task", middlewares.AuthNotGuest))
|
||||||
|
_sharing(auth.Group("/share", middlewares.AuthNotGuest))
|
||||||
admin(auth.Group("/admin", middlewares.AuthAdmin))
|
admin(auth.Group("/admin", middlewares.AuthAdmin))
|
||||||
if flags.Debug || flags.Dev {
|
if flags.Debug || flags.Dev {
|
||||||
debug(g.Group("/debug"))
|
debug(g.Group("/debug"))
|
||||||
@ -169,10 +180,16 @@ func admin(g *gin.RouterGroup) {
|
|||||||
index.GET("/progress", middlewares.SearchIndex, handles.GetProgress)
|
index.GET("/progress", middlewares.SearchIndex, handles.GetProgress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fsAndShare(g *gin.RouterGroup) {
|
||||||
|
g.Any("/list", handles.FsListSplit)
|
||||||
|
g.Any("/get", handles.FsGetSplit)
|
||||||
|
a := g.Group("/archive")
|
||||||
|
a.Any("/meta", handles.FsArchiveMetaSplit)
|
||||||
|
a.Any("/list", handles.FsArchiveListSplit)
|
||||||
|
}
|
||||||
|
|
||||||
func _fs(g *gin.RouterGroup) {
|
func _fs(g *gin.RouterGroup) {
|
||||||
g.Any("/list", handles.FsList)
|
|
||||||
g.Any("/search", middlewares.SearchIndex, handles.Search)
|
g.Any("/search", middlewares.SearchIndex, handles.Search)
|
||||||
g.Any("/get", handles.FsGet)
|
|
||||||
g.Any("/other", handles.FsOther)
|
g.Any("/other", handles.FsOther)
|
||||||
g.Any("/dirs", handles.FsDirs)
|
g.Any("/dirs", handles.FsDirs)
|
||||||
g.POST("/mkdir", handles.FsMkdir)
|
g.POST("/mkdir", handles.FsMkdir)
|
||||||
@ -192,16 +209,23 @@ func _fs(g *gin.RouterGroup) {
|
|||||||
// g.POST("/add_qbit", handles.AddQbittorrent)
|
// g.POST("/add_qbit", handles.AddQbittorrent)
|
||||||
// g.POST("/add_transmission", handles.SetTransmission)
|
// g.POST("/add_transmission", handles.SetTransmission)
|
||||||
g.POST("/add_offline_download", handles.AddOfflineDownload)
|
g.POST("/add_offline_download", handles.AddOfflineDownload)
|
||||||
a := g.Group("/archive")
|
g.POST("/archive/decompress", handles.FsArchiveDecompress)
|
||||||
a.Any("/meta", handles.FsArchiveMeta)
|
|
||||||
a.Any("/list", handles.FsArchiveList)
|
|
||||||
a.POST("/decompress", handles.FsArchiveDecompress)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func _task(g *gin.RouterGroup) {
|
func _task(g *gin.RouterGroup) {
|
||||||
handles.SetupTaskRoute(g)
|
handles.SetupTaskRoute(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _sharing(g *gin.RouterGroup) {
|
||||||
|
g.Any("/list", handles.ListSharings)
|
||||||
|
g.GET("/get", handles.GetSharing)
|
||||||
|
g.POST("/create", handles.CreateSharing)
|
||||||
|
g.POST("/update", handles.UpdateSharing)
|
||||||
|
g.POST("/delete", handles.DeleteSharing)
|
||||||
|
g.POST("/enable", handles.SetEnableSharing(false))
|
||||||
|
g.POST("/disable", handles.SetEnableSharing(true))
|
||||||
|
}
|
||||||
|
|
||||||
func Cors(r *gin.Engine) {
|
func Cors(r *gin.Engine) {
|
||||||
config := cors.DefaultConfig()
|
config := cors.DefaultConfig()
|
||||||
// config.AllowAllOrigins = true
|
// config.AllowAllOrigins = true
|
||||||
|
Reference in New Issue
Block a user