Compare commits

...

11 Commits

15 changed files with 166 additions and 63 deletions

View File

@ -234,7 +234,10 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
buf := make([]byte, 8) buf := make([]byte, 8)
r, _ := new(big.Int).SetString(utils.GetMD5Encode(d.AccessToken)[:16], 16) r, _ := new(big.Int).SetString(utils.GetMD5Encode(d.AccessToken)[:16], 16)
i := new(big.Int).SetInt64(file.GetSize()) i := new(big.Int).SetInt64(file.GetSize())
o := r.Mod(r, i) o := new(big.Int).SetInt64(0)
if file.GetSize() > 0 {
o = r.Mod(r, i)
}
n, _ := io.NewSectionReader(tempFile, o.Int64(), 8).Read(buf[:8]) n, _ := io.NewSectionReader(tempFile, o.Int64(), 8).Read(buf[:8])
reqBody["proof_code"] = base64.StdEncoding.EncodeToString(buf[:n]) reqBody["proof_code"] = base64.StdEncoding.EncodeToString(buf[:n])

View File

@ -130,13 +130,10 @@ func (d *S3) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
} }
func (d *S3) Remove(ctx context.Context, obj model.Obj) error { func (d *S3) Remove(ctx context.Context, obj model.Obj) error {
key := getKey(obj.GetPath(), obj.IsDir()) if obj.IsDir() {
input := &s3.DeleteObjectInput{ return d.removeDir(ctx, obj.GetPath())
Bucket: &d.Bucket,
Key: &key,
} }
_, err := d.client.DeleteObject(input) return d.removeFile(obj.GetPath())
return err
} }
func (d *S3) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error { func (d *S3) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {

View File

@ -52,12 +52,11 @@ func getKey(path string, dir bool) string {
return path return path
} }
var defaultPlaceholderName = ".placeholder" // var defaultPlaceholderName = ".placeholder"
func getPlaceholderName(placeholder string) string { func getPlaceholderName(placeholder string) string {
if placeholder == "" { //if placeholder == "" {
return defaultPlaceholderName // return defaultPlaceholderName
} //}
return placeholder return placeholder
} }
@ -205,3 +204,33 @@ func (d *S3) copyDir(ctx context.Context, src string, dst string) error {
} }
return nil return nil
} }
func (d *S3) removeDir(ctx context.Context, src string) error {
objs, err := op.List(ctx, d, src, model.ListArgs{})
if err != nil {
return err
}
for _, obj := range objs {
cSrc := path.Join(src, obj.GetName())
if obj.IsDir() {
err = d.removeDir(ctx, cSrc)
} else {
err = d.removeFile(cSrc)
}
if err != nil {
return err
}
}
_ = d.removeFile(path.Join(src, getPlaceholderName(d.Placeholder)))
return nil
}
func (d *S3) removeFile(src string) error {
key := getKey(src, true)
input := &s3.DeleteObjectInput{
Bucket: &d.Bucket,
Key: &key,
}
_, err := d.client.DeleteObject(input)
return err
}

View File

@ -146,6 +146,7 @@ func (x *ThunderExpert) Init(ctx context.Context, storage model.Storage) (err er
PackageName: x.PackageName, PackageName: x.PackageName,
UserAgent: x.UserAgent, UserAgent: x.UserAgent,
DownloadUserAgent: x.DownloadUserAgent, DownloadUserAgent: x.DownloadUserAgent,
UseVideoUrl: x.UseVideoUrl,
}, },
} }
@ -209,6 +210,7 @@ func (x *ThunderExpert) Init(ctx context.Context, storage model.Storage) (err er
} }
x.XunLeiCommon.UserAgent = x.UserAgent x.XunLeiCommon.UserAgent = x.UserAgent
x.XunLeiCommon.DownloadUserAgent = x.DownloadUserAgent x.XunLeiCommon.DownloadUserAgent = x.DownloadUserAgent
x.XunLeiCommon.UseVideoUrl = x.UseVideoUrl
} }
return nil return nil
} }
@ -252,6 +254,15 @@ func (xc *XunLeiCommon) Link(ctx context.Context, file model.Obj, args model.Lin
}, },
} }
if xc.UseVideoUrl {
for _, media := range lFile.Medias {
if media.Link.URL != "" {
link.URL = media.Link.URL
break
}
}
}
/* /*
strs := regexp.MustCompile(`e=([0-9]*)`).FindStringSubmatch(lFile.WebContentLink) strs := regexp.MustCompile(`e=([0-9]*)`).FindStringSubmatch(lFile.WebContentLink)
if len(strs) == 2 { if len(strs) == 2 {

View File

@ -41,6 +41,9 @@ type ExpertAddition struct {
//不影响登录,影响下载速度 //不影响登录,影响下载速度
UserAgent string `json:"user_agent" required:"true" default:"ANDROID-com.xunlei.downloadprovider/7.51.0.8196 netWorkType/4G appid/40 deviceName/Xiaomi_M2004j7ac deviceModel/M2004J7AC OSVersion/12 protocolVersion/301 platformVersion/10 sdkVersion/220200 Oauth2Client/0.9 (Linux 4_14_186-perf-gdcf98eab238b) (JAVA 0)"` UserAgent string `json:"user_agent" required:"true" default:"ANDROID-com.xunlei.downloadprovider/7.51.0.8196 netWorkType/4G appid/40 deviceName/Xiaomi_M2004j7ac deviceModel/M2004J7AC OSVersion/12 protocolVersion/301 platformVersion/10 sdkVersion/220200 Oauth2Client/0.9 (Linux 4_14_186-perf-gdcf98eab238b) (JAVA 0)"`
DownloadUserAgent string `json:"download_user_agent" required:"true" default:"Dalvik/2.1.0 (Linux; U; Android 12; M2004J7AC Build/SP1A.210812.016)"` DownloadUserAgent string `json:"download_user_agent" required:"true" default:"Dalvik/2.1.0 (Linux; U; Android 12; M2004J7AC Build/SP1A.210812.016)"`
//优先使用视频链接代替下载链接
UseVideoUrl bool `json:"use_video_url"`
} }
// 登录特征,用于判断是否重新登录 // 登录特征,用于判断是否重新登录

View File

@ -77,6 +77,13 @@ type FileList struct {
VersionOutdated bool `json:"version_outdated"` VersionOutdated bool `json:"version_outdated"`
} }
type Link struct {
URL string `json:"url"`
Token string `json:"token"`
Expire time.Time `json:"expire"`
Type string `json:"type"`
}
type Files struct { type Files struct {
Kind string `json:"kind"` Kind string `json:"kind"`
ID string `json:"id"` ID string `json:"id"`
@ -95,26 +102,26 @@ type Files struct {
ThumbnailLink string `json:"thumbnail_link"` ThumbnailLink string `json:"thumbnail_link"`
//Md5Checksum string `json:"md5_checksum"` //Md5Checksum string `json:"md5_checksum"`
//Hash string `json:"hash"` //Hash string `json:"hash"`
//Links struct{} `json:"links"` Links map[string]Link `json:"links"`
Phase string `json:"phase"` Phase string `json:"phase"`
Audit struct { Audit struct {
Status string `json:"status"` Status string `json:"status"`
Message string `json:"message"` Message string `json:"message"`
Title string `json:"title"` Title string `json:"title"`
} `json:"audit"` } `json:"audit"`
/* Medias []struct { Medias []struct {
Category string `json:"category"` Category string `json:"category"`
IconLink string `json:"icon_link"` IconLink string `json:"icon_link"`
IsDefault bool `json:"is_default"` IsDefault bool `json:"is_default"`
IsOrigin bool `json:"is_origin"` IsOrigin bool `json:"is_origin"`
IsVisible bool `json:"is_visible"` IsVisible bool `json:"is_visible"`
//Link interface{} `json:"link"` Link Link `json:"link"`
MediaID string `json:"media_id"` MediaID string `json:"media_id"`
MediaName string `json:"media_name"` MediaName string `json:"media_name"`
NeedMoreQuota bool `json:"need_more_quota"` NeedMoreQuota bool `json:"need_more_quota"`
Priority int `json:"priority"` Priority int `json:"priority"`
RedirectLink string `json:"redirect_link"` RedirectLink string `json:"redirect_link"`
ResolutionName string `json:"resolution_name"` ResolutionName string `json:"resolution_name"`
Video struct { Video struct {
AudioCodec string `json:"audio_codec"` AudioCodec string `json:"audio_codec"`
BitRate int `json:"bit_rate"` BitRate int `json:"bit_rate"`
@ -126,7 +133,7 @@ type Files struct {
Width int `json:"width"` Width int `json:"width"`
} `json:"video"` } `json:"video"`
VipTypes []string `json:"vip_types"` VipTypes []string `json:"vip_types"`
} `json:"medias"` */ } `json:"medias"`
Trashed bool `json:"trashed"` Trashed bool `json:"trashed"`
DeleteTime string `json:"delete_time"` DeleteTime string `json:"delete_time"`
OriginalURL string `json:"original_url"` OriginalURL string `json:"original_url"`

View File

@ -52,6 +52,7 @@ type Common struct {
PackageName string PackageName string
UserAgent string UserAgent string
DownloadUserAgent string DownloadUserAgent string
UseVideoUrl bool
} }
func (c *Common) SetCaptchaToken(captchaToken string) { func (c *Common) SetCaptchaToken(captchaToken string) {

View File

@ -76,10 +76,15 @@ func (m *Monitor) Update() (bool, error) {
} }
m.retried = 0 m.retried = 0
if len(info.FollowedBy) != 0 { if len(info.FollowedBy) != 0 {
log.Debugf("followen by: %+v", info.FollowedBy)
gid := info.FollowedBy[0] gid := info.FollowedBy[0]
notify.Signals.Delete(m.tsk.ID) notify.Signals.Delete(m.tsk.ID)
oldId := m.tsk.ID
m.tsk.ID = gid m.tsk.ID = gid
DownTaskManager.RawTasks().Delete(oldId)
DownTaskManager.RawTasks().Store(m.tsk.ID, m.tsk)
notify.Signals.Store(gid, m.c) notify.Signals.Store(gid, m.c)
return false, nil
} }
// update download status // update download status
total, err := strconv.ParseUint(info.TotalLength, 10, 64) total, err := strconv.ParseUint(info.TotalLength, 10, 64)
@ -120,6 +125,7 @@ func (m *Monitor) Complete() error {
} }
// get files // get files
files, err := client.GetFiles(m.tsk.ID) files, err := client.GetFiles(m.tsk.ID)
log.Debugf("files len: %d", len(files))
if err != nil { if err != nil {
return errors.Wrapf(err, "failed to get files of %s", m.tsk.ID) return errors.Wrapf(err, "failed to get files of %s", m.tsk.ID)
} }
@ -134,7 +140,8 @@ func (m *Monitor) Complete() error {
log.Errorf("failed to remove aria2 temp dir: %+v", err.Error()) log.Errorf("failed to remove aria2 temp dir: %+v", err.Error())
} }
}() }()
for _, file := range files { for i, _ := range files {
file := files[i]
TransferTaskManager.Submit(task.WithCancelCtx(&task.Task[uint64]{ TransferTaskManager.Submit(task.WithCancelCtx(&task.Task[uint64]{
Name: fmt.Sprintf("transfer %s to [%s](%s)", file.Path, storage.GetStorage().MountPath, dstDirActualPath), Name: fmt.Sprintf("transfer %s to [%s](%s)", file.Path, storage.GetStorage().MountPath, dstDirActualPath),
Func: func(tsk *task.Task[uint64]) error { Func: func(tsk *task.Task[uint64]) error {

View File

@ -53,9 +53,6 @@ func InitDB() {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&tls=%s", dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&tls=%s",
database.User, database.Password, database.Host, database.Port, database.Name, database.SSLMode) database.User, database.Password, database.Host, database.Port, database.Name, database.SSLMode)
dB, err = gorm.Open(mysql.Open(dsn), gormConfig) dB, err = gorm.Open(mysql.Open(dsn), gormConfig)
if err == nil {
dB = dB.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4")
}
} }
case "postgres": case "postgres":
{ {

View File

@ -3,6 +3,7 @@ package db
import ( import (
"log" "log"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/model" "github.com/alist-org/alist/v3/internal/model"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -11,7 +12,12 @@ var db *gorm.DB
func Init(d *gorm.DB) { func Init(d *gorm.DB) {
db = d db = d
err := db.AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem)) var err error
if conf.Conf.Database.Type == "mysql" {
err = db.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4").AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem))
} else {
err = db.AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem))
}
if err != nil { if err != nil {
log.Fatalf("failed migrate database: %s", err.Error()) log.Fatalf("failed migrate database: %s", err.Error())
} }

View File

@ -16,37 +16,43 @@ import (
func list(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error) { func list(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error) {
meta := ctx.Value("meta").(*model.Meta) meta := ctx.Value("meta").(*model.Meta)
user := ctx.Value("user").(*model.User) user := ctx.Value("user").(*model.User)
var objs []model.Obj
storage, actualPath, err := op.GetStorageAndActualPath(path) storage, actualPath, err := op.GetStorageAndActualPath(path)
virtualFiles := op.GetStorageVirtualFilesByPath(path) virtualFiles := op.GetStorageVirtualFilesByPath(path)
if err != nil { if err != nil {
if len(virtualFiles) != 0 { if len(virtualFiles) == 0 {
return virtualFiles, nil return nil, errors.WithMessage(err, "failed get storage")
} }
return nil, errors.WithMessage(err, "failed get storage") } else {
} objs, err = op.List(ctx, storage, actualPath, model.ListArgs{
objs, err := op.List(ctx, storage, actualPath, model.ListArgs{ ReqPath: path,
ReqPath: path, }, refresh...)
}, refresh...) if err != nil {
if err != nil { log.Errorf("%+v", err)
log.Errorf("%+v", err) if len(virtualFiles) == 0 {
if len(virtualFiles) != 0 { return nil, errors.WithMessage(err, "failed get objs")
return virtualFiles, nil }
} }
return nil, errors.WithMessage(err, "failed get objs")
} }
for _, storageFile := range virtualFiles { if objs == nil {
if !containsByName(objs, storageFile) { objs = virtualFiles
objs = append(objs, storageFile) } else {
for _, storageFile := range virtualFiles {
if !containsByName(objs, storageFile) {
objs = append(objs, storageFile)
}
} }
} }
if whetherHide(user, meta, path) { if whetherHide(user, meta, path) {
objs = hide(objs, meta) objs = hide(objs, meta)
} }
// sort objs // sort objs
if storage.Config().LocalSort { if storage != nil {
model.SortFiles(objs, storage.GetStorage().OrderBy, storage.GetStorage().OrderDirection) if storage.Config().LocalSort {
model.SortFiles(objs, storage.GetStorage().OrderBy, storage.GetStorage().OrderDirection)
}
model.ExtractFolder(objs, storage.GetStorage().ExtractFolder)
} }
model.ExtractFolder(objs, storage.GetStorage().ExtractFolder)
return objs, nil return objs, nil
} }

View File

@ -52,7 +52,7 @@ func List(ctx context.Context, storage driver.Driver, path string, args model.Li
} }
key := Key(storage, path) key := Key(storage, path)
if len(refresh) == 0 || !refresh[0] { if len(refresh) == 0 || !refresh[0] {
if files, ok := listCache.Get(key); ok { if files, ok := listCache.Get(key); ok && len(files) > 0 {
return files, nil return files, nil
} }
} }

View File

@ -122,6 +122,10 @@ func (tm *Manager[K]) ClearDone() {
tm.RemoveByStates(SUCCEEDED, CANCELED, ERRORED) tm.RemoveByStates(SUCCEEDED, CANCELED, ERRORED)
} }
func (tm *Manager[K]) RawTasks() *generic_sync.MapOf[K, *Task[K]] {
return &tm.tasks
}
func NewTaskManager[K comparable](maxWorker int, updateID ...func(*K)) *Manager[K] { func NewTaskManager[K comparable](maxWorker int, updateID ...func(*K)) *Manager[K] {
tm := &Manager[K]{ tm := &Manager[K]{
tasks: generic_sync.MapOf[K, *Task[K]]{}, tasks: generic_sync.MapOf[K, *Task[K]]{},

View File

@ -3,7 +3,9 @@ package utils
import ( import (
"crypto/md5" "crypto/md5"
"crypto/sha1" "crypto/sha1"
"encoding/base64"
"encoding/hex" "encoding/hex"
"strings"
) )
func GetSHA1Encode(data string) string { func GetSHA1Encode(data string) string {
@ -17,3 +19,20 @@ func GetMD5Encode(data string) string {
h.Write([]byte(data)) h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil)) return hex.EncodeToString(h.Sum(nil))
} }
var DEC = map[string]string{
"-": "+",
"_": "/",
".": "=",
}
func SafeAtob(data string) (string, error) {
for k, v := range DEC {
data = strings.ReplaceAll(data, k, v)
}
bytes, err := base64.StdEncoding.DecodeString(data)
if err != nil {
return "", err
}
return string(bytes), err
}

View File

@ -7,9 +7,9 @@ import (
"github.com/alist-org/alist/v3/internal/conf" "github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/setting" "github.com/alist-org/alist/v3/internal/setting"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/server/common" "github.com/alist-org/alist/v3/server/common"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
) )
func Favicon(c *gin.Context) { func Favicon(c *gin.Context) {
@ -18,20 +18,33 @@ func Favicon(c *gin.Context) {
func Plist(c *gin.Context) { func Plist(c *gin.Context) {
link := c.Param("link") link := c.Param("link")
u, err := url.PathUnescape(link) u, err := utils.SafeAtob(link)
if err != nil { if err != nil {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 400)
return return
} }
uUrl, err := url.Parse(u) uUrl, err := url.Parse(u)
if err != nil { if err != nil {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 400)
return return
} }
name := c.Param("name") fullName := c.Param("name")
log.Debug("name", name) Url := uUrl.String()
u = uUrl.String() fullName = strings.TrimSuffix(fullName, ".plist")
name = strings.TrimSuffix(name, ".plist") fullName, err = utils.SafeAtob(fullName)
if err != nil {
common.ErrorResp(c, err, 400)
return
}
name := fullName
identifier := fmt.Sprintf("ci.nn.%s", url.PathEscape(fullName))
sep := "@"
if strings.Contains(fullName, sep) {
ss := strings.Split(fullName, sep)
name = strings.Join(ss[:len(ss)-1], sep)
identifier = ss[len(ss)-1]
}
name = strings.ReplaceAll(name, "<", "[") name = strings.ReplaceAll(name, "<", "[")
name = strings.ReplaceAll(name, ">", "]") name = strings.ReplaceAll(name, ">", "]")
plist := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> plist := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@ -46,13 +59,13 @@ func Plist(c *gin.Context) {
<key>kind</key> <key>kind</key>
<string>software-package</string> <string>software-package</string>
<key>url</key> <key>url</key>
<string>%s</string> <string><![CDATA[%s]]></string>
</dict> </dict>
</array> </array>
<key>metadata</key> <key>metadata</key>
<dict> <dict>
<key>bundle-identifier</key> <key>bundle-identifier</key>
<string>ci.nn.%s</string> <string>%s</string>
<key>bundle-version</key> <key>bundle-version</key>
<string>4.4</string> <string>4.4</string>
<key>kind</key> <key>kind</key>
@ -63,7 +76,7 @@ func Plist(c *gin.Context) {
</dict> </dict>
</array> </array>
</dict> </dict>
</plist>`, u, url.PathEscape(name), name) </plist>`, Url, identifier, name)
c.Header("Content-Type", "application/xml;charset=utf-8") c.Header("Content-Type", "application/xml;charset=utf-8")
c.Status(200) c.Status(200)
_, _ = c.Writer.WriteString(plist) _, _ = c.Writer.WriteString(plist)