Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/123_link/parse.go
1986 views
1
package _123Link
2
3
import (
4
"fmt"
5
url2 "net/url"
6
stdpath "path"
7
"strconv"
8
"strings"
9
"time"
10
)
11
12
// build tree from text, text structure definition:
13
/**
14
* FolderName:
15
* [FileSize:][Modified:]Url
16
*/
17
/**
18
* For example:
19
* folder1:
20
* name1:url1
21
* url2
22
* folder2:
23
* url3
24
* url4
25
* url5
26
* folder3:
27
* url6
28
* url7
29
* url8
30
*/
31
// if there are no name, use the last segment of url as name
32
func BuildTree(text string) (*Node, error) {
33
lines := strings.Split(text, "\n")
34
var root = &Node{Level: -1, Name: "root"}
35
stack := []*Node{root}
36
for _, line := range lines {
37
// calculate indent
38
indent := 0
39
for i := 0; i < len(line); i++ {
40
if line[i] != ' ' {
41
break
42
}
43
indent++
44
}
45
// if indent is not a multiple of 2, it is an error
46
if indent%2 != 0 {
47
return nil, fmt.Errorf("the line '%s' is not a multiple of 2", line)
48
}
49
// calculate level
50
level := indent / 2
51
line = strings.TrimSpace(line[indent:])
52
// if the line is empty, skip
53
if line == "" {
54
continue
55
}
56
// if level isn't greater than the level of the top of the stack
57
// it is not the child of the top of the stack
58
for level <= stack[len(stack)-1].Level {
59
// pop the top of the stack
60
stack = stack[:len(stack)-1]
61
}
62
// if the line is a folder
63
if isFolder(line) {
64
// create a new node
65
node := &Node{
66
Level: level,
67
Name: strings.TrimSuffix(line, ":"),
68
}
69
// add the node to the top of the stack
70
stack[len(stack)-1].Children = append(stack[len(stack)-1].Children, node)
71
// push the node to the stack
72
stack = append(stack, node)
73
} else {
74
// if the line is a file
75
// create a new node
76
node, err := parseFileLine(line)
77
if err != nil {
78
return nil, err
79
}
80
node.Level = level
81
// add the node to the top of the stack
82
stack[len(stack)-1].Children = append(stack[len(stack)-1].Children, node)
83
}
84
}
85
return root, nil
86
}
87
88
func isFolder(line string) bool {
89
return strings.HasSuffix(line, ":")
90
}
91
92
// line definition:
93
// [FileSize:][Modified:]Url
94
func parseFileLine(line string) (*Node, error) {
95
// if there is no url, it is an error
96
if !strings.Contains(line, "http://") && !strings.Contains(line, "https://") {
97
return nil, fmt.Errorf("invalid line: %s, because url is required for file", line)
98
}
99
index := strings.Index(line, "http://")
100
if index == -1 {
101
index = strings.Index(line, "https://")
102
}
103
url := line[index:]
104
info := line[:index]
105
node := &Node{
106
Url: url,
107
}
108
name := stdpath.Base(url)
109
unescape, err := url2.PathUnescape(name)
110
if err == nil {
111
name = unescape
112
}
113
node.Name = name
114
if index > 0 {
115
if !strings.HasSuffix(info, ":") {
116
return nil, fmt.Errorf("invalid line: %s, because file info must end with ':'", line)
117
}
118
info = info[:len(info)-1]
119
if info == "" {
120
return nil, fmt.Errorf("invalid line: %s, because file name can't be empty", line)
121
}
122
infoParts := strings.Split(info, ":")
123
size, err := strconv.ParseInt(infoParts[0], 10, 64)
124
if err != nil {
125
return nil, fmt.Errorf("invalid line: %s, because file size must be an integer", line)
126
}
127
node.Size = size
128
if len(infoParts) > 1 {
129
modified, err := strconv.ParseInt(infoParts[1], 10, 64)
130
if err != nil {
131
return nil, fmt.Errorf("invalid line: %s, because file modified must be an unix timestamp", line)
132
}
133
node.Modified = modified
134
} else {
135
node.Modified = time.Now().Unix()
136
}
137
}
138
return node, nil
139
}
140
141
func splitPath(path string) []string {
142
if path == "/" {
143
return []string{"root"}
144
}
145
parts := strings.Split(path, "/")
146
parts[0] = "root"
147
return parts
148
}
149
150
func GetNodeFromRootByPath(root *Node, path string) *Node {
151
return root.getByPath(splitPath(path))
152
}
153
154