Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(url-tree): support url tree driver writing #7779

Merged
merged 3 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 180 additions & 1 deletion drivers/url_tree/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ package url_tree

import (
"context"
"errors"
"github.com/alist-org/alist/v3/internal/op"
stdpath "path"
"strings"
"sync"

"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/errs"
Expand All @@ -14,7 +18,8 @@ import (
type Urls struct {
model.Storage
Addition
root *Node
root *Node
mutex sync.RWMutex
}

func (d *Urls) Config() driver.Config {
Expand All @@ -40,11 +45,15 @@ func (d *Urls) Drop(ctx context.Context) error {
}

func (d *Urls) Get(ctx context.Context, path string) (model.Obj, error) {
d.mutex.RLock()
defer d.mutex.RUnlock()
node := GetNodeFromRootByPath(d.root, path)
return nodeToObj(node, path)
}

func (d *Urls) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
d.mutex.RLock()
defer d.mutex.RUnlock()
node := GetNodeFromRootByPath(d.root, dir.GetPath())
log.Debugf("path: %s, node: %+v", dir.GetPath(), node)
if node == nil {
Expand All @@ -59,6 +68,8 @@ func (d *Urls) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]
}

func (d *Urls) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
d.mutex.RLock()
defer d.mutex.RUnlock()
node := GetNodeFromRootByPath(d.root, file.GetPath())
log.Debugf("path: %s, node: %+v", file.GetPath(), node)
if node == nil {
Expand All @@ -72,6 +83,174 @@ func (d *Urls) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*
return nil, errs.NotFile
}

func (d *Urls) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
if !d.Writable {
return nil, errs.PermissionDenied
}
d.mutex.Lock()
defer d.mutex.Unlock()
node := GetNodeFromRootByPath(d.root, parentDir.GetPath())
if node == nil {
return nil, errs.ObjectNotFound
}
if node.isFile() {
return nil, errs.NotFolder
}
dir := &Node{
Name: dirName,
Level: node.Level + 1,
}
node.Children = append(node.Children, dir)
d.updateStorage()
return nodeToObj(dir, stdpath.Join(parentDir.GetPath(), dirName))
}

func (d *Urls) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
if !d.Writable {
return nil, errs.PermissionDenied
}
if strings.HasPrefix(dstDir.GetPath(), srcObj.GetPath()) {
return nil, errors.New("cannot move parent dir to child")
}
d.mutex.Lock()
defer d.mutex.Unlock()
dstNode := GetNodeFromRootByPath(d.root, dstDir.GetPath())
if dstNode == nil || dstNode.isFile() {
return nil, errs.NotFolder
}
srcDir, srcName := stdpath.Split(srcObj.GetPath())
srcParentNode := GetNodeFromRootByPath(d.root, srcDir)
if srcParentNode == nil {
return nil, errs.ObjectNotFound
}
newChildren := make([]*Node, 0, len(srcParentNode.Children))
var srcNode *Node
for _, child := range srcParentNode.Children {
if child.Name == srcName {
srcNode = child
} else {
newChildren = append(newChildren, child)
}
}
if srcNode == nil {
return nil, errs.ObjectNotFound
}
srcParentNode.Children = newChildren
srcNode.setLevel(dstNode.Level + 1)
dstNode.Children = append(dstNode.Children, srcNode)
d.root.calSize()
d.updateStorage()
return nodeToObj(srcNode, stdpath.Join(dstDir.GetPath(), srcName))
}

func (d *Urls) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
if !d.Writable {
return nil, errs.PermissionDenied
}
d.mutex.Lock()
defer d.mutex.Unlock()
srcNode := GetNodeFromRootByPath(d.root, srcObj.GetPath())
if srcNode == nil {
return nil, errs.ObjectNotFound
}
srcNode.Name = newName
d.updateStorage()
return nodeToObj(srcNode, stdpath.Join(stdpath.Dir(srcObj.GetPath()), newName))
}

func (d *Urls) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
if !d.Writable {
return nil, errs.PermissionDenied
}
if strings.HasPrefix(dstDir.GetPath(), srcObj.GetPath()) {
return nil, errors.New("cannot copy parent dir to child")
}
d.mutex.Lock()
defer d.mutex.Unlock()
dstNode := GetNodeFromRootByPath(d.root, dstDir.GetPath())
if dstNode == nil || dstNode.isFile() {
return nil, errs.NotFolder
}
srcNode := GetNodeFromRootByPath(d.root, srcObj.GetPath())
if srcNode == nil {
return nil, errs.ObjectNotFound
}
newNode := srcNode.deepCopy(dstNode.Level + 1)
dstNode.Children = append(dstNode.Children, newNode)
d.root.calSize()
d.updateStorage()
return nodeToObj(newNode, stdpath.Join(dstDir.GetPath(), stdpath.Base(srcObj.GetPath())))
}

func (d *Urls) Remove(ctx context.Context, obj model.Obj) error {
if !d.Writable {
return errs.PermissionDenied
}
d.mutex.Lock()
defer d.mutex.Unlock()
objDir, objName := stdpath.Split(obj.GetPath())
nodeParent := GetNodeFromRootByPath(d.root, objDir)
if nodeParent == nil {
return errs.ObjectNotFound
}
newChildren := make([]*Node, 0, len(nodeParent.Children))
var deletedObj *Node
for _, child := range nodeParent.Children {
if child.Name != objName {
newChildren = append(newChildren, child)
} else {
deletedObj = child
}
}
if deletedObj == nil {
return errs.ObjectNotFound
}
nodeParent.Children = newChildren
if deletedObj.Size > 0 {
d.root.calSize()
}
d.updateStorage()
return nil
}

func (d *Urls) PutURL(ctx context.Context, dstDir model.Obj, name, url string) (model.Obj, error) {
if !d.Writable {
return nil, errs.PermissionDenied
}
d.mutex.Lock()
defer d.mutex.Unlock()
dirNode := GetNodeFromRootByPath(d.root, dstDir.GetPath())
if dirNode == nil || dirNode.isFile() {
return nil, errs.NotFolder
}
newNode := &Node{
Name: name,
Level: dirNode.Level + 1,
Url: url,
}
dirNode.Children = append(dirNode.Children, newNode)
if d.HeadSize {
size, err := getSizeFromUrl(url)
if err != nil {
log.Errorf("get size from url error: %s", err)
} else {
newNode.Size = size
d.root.calSize()
}
}
d.updateStorage()
return nodeToObj(newNode, stdpath.Join(dstDir.GetPath(), name))
}

func (d *Urls) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
return errs.UploadNotSupported
}

func (d *Urls) updateStorage() {
d.UrlStructure = StringifyTree(d.root)
op.MustSaveDriverStorage(d)
}

//func (d *Template) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
// return nil, errs.NotSupport
//}
Expand Down
3 changes: 2 additions & 1 deletion drivers/url_tree/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type Addition struct {
// define other
UrlStructure string `json:"url_structure" type:"text" required:"true" default:"https://jsd.nn.ci/gh/alist-org/alist/README.md\nhttps://jsd.nn.ci/gh/alist-org/alist/README_cn.md\nfolder:\n CONTRIBUTING.md:1635:https://jsd.nn.ci/gh/alist-org/alist/CONTRIBUTING.md\n CODE_OF_CONDUCT.md:2093:https://jsd.nn.ci/gh/alist-org/alist/CODE_OF_CONDUCT.md" help:"structure:FolderName:\n [FileName:][FileSize:][Modified:]Url"`
HeadSize bool `json:"head_size" type:"bool" default:"false" help:"Use head method to get file size, but it may be failed."`
Writable bool `json:"writable" type:"bool" default:"false"`
}

var config = driver.Config{
Expand All @@ -20,7 +21,7 @@ var config = driver.Config{
OnlyLocal: false,
OnlyProxy: false,
NoCache: true,
NoUpload: true,
NoUpload: false,
NeedMs: false,
DefaultRoot: "",
CheckStatus: true,
Expand Down
18 changes: 18 additions & 0 deletions drivers/url_tree/types.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package url_tree

import "github.com/alist-org/alist/v3/pkg/utils"

// Node is a node in the folder tree
type Node struct {
Url string
Expand Down Expand Up @@ -44,3 +46,19 @@ func (node *Node) calSize() int64 {
node.Size = size
return size
}

func (node *Node) setLevel(level int) {
node.Level = level
for _, child := range node.Children {
child.setLevel(level + 1)
}
}

func (node *Node) deepCopy(level int) *Node {
ret := *node
ret.Level = level
ret.Children, _ = utils.SliceConvert(ret.Children, func(child *Node) (*Node, error) {
return child.deepCopy(level + 1), nil
})
return &ret
}
46 changes: 46 additions & 0 deletions drivers/url_tree/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ func splitPath(path string) []string {
if path == "/" {
return []string{"root"}
}
if strings.HasSuffix(path, "/") {
path = path[:len(path)-1]
}
parts := strings.Split(path, "/")
parts[0] = "root"
return parts
Expand Down Expand Up @@ -190,3 +193,46 @@ func getSizeFromUrl(url string) (int64, error) {
}
return size, nil
}

func StringifyTree(node *Node) string {
sb := strings.Builder{}
if node.Level == -1 {
for i, child := range node.Children {
sb.WriteString(StringifyTree(child))
if i < len(node.Children)-1 {
sb.WriteString("\n")
}
}
return sb.String()
}
for i := 0; i < node.Level; i++ {
sb.WriteString(" ")
}
if node.Url == "" {
sb.WriteString(node.Name)
sb.WriteString(":")
for _, child := range node.Children {
sb.WriteString("\n")
sb.WriteString(StringifyTree(child))
}
} else if node.Size == 0 && node.Modified == 0 {
if stdpath.Base(node.Url) == node.Name {
sb.WriteString(node.Url)
} else {
sb.WriteString(fmt.Sprintf("%s:%s", node.Name, node.Url))
}
} else {
sb.WriteString(node.Name)
sb.WriteString(":")
if node.Size != 0 || node.Modified != 0 {
sb.WriteString(strconv.FormatInt(node.Size, 10))
sb.WriteString(":")
}
if node.Modified != 0 {
sb.WriteString(strconv.FormatInt(node.Modified, 10))
sb.WriteString(":")
}
sb.WriteString(node.Url)
}
return sb.String()
}
14 changes: 14 additions & 0 deletions internal/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ type Put interface {
Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up UpdateProgress) error
}

type PutURL interface {
// PutURL directly put a URL into the storage
// Applicable to index-based drivers like URL-Tree or drivers that support uploading files as URLs
// Called when using SimpleHttp for offline downloading, skipping creating a download task
PutURL(ctx context.Context, dstDir model.Obj, name, url string) error
}

//type WriteResult interface {
// MkdirResult
// MoveResult
Expand Down Expand Up @@ -109,6 +116,13 @@ type PutResult interface {
Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up UpdateProgress) (model.Obj, error)
}

type PutURLResult interface {
// PutURL directly put a URL into the storage
// Applicable to index-based drivers like URL-Tree or drivers that support uploading files as URLs
// Called when using SimpleHttp for offline downloading, skipping creating a download task
PutURL(ctx context.Context, dstDir model.Obj, name, url string) (model.Obj, error)
}

type UpdateProgress func(percentage float64)

type Progress struct {
Expand Down
Loading
Loading