-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuser_assets.go
More file actions
125 lines (111 loc) · 3.47 KB
/
Copy pathuser_assets.go
File metadata and controls
125 lines (111 loc) · 3.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package main
import (
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
)
const (
packagedImagesPath = "images"
packagedAudioPath = "audio"
)
// ensureExternalImages 将发布包中的默认图片释放到用户数据目录。
// 已存在的文件始终视为用户自定义内容,不会被默认资源覆盖。
func ensureExternalImages(packagedAssets fs.FS, imagesDir string) (int, error) {
return ensureExternalAssetDirectory(packagedAssets, packagedImagesPath, imagesDir, "图片")
}
// ensureExternalAudio 将默认音频释放到用户数据目录,并保留用户同名替换。
func ensureExternalAudio(packagedAssets fs.FS, audioDir string) (int, error) {
return ensureExternalAssetDirectory(packagedAssets, packagedAudioPath, audioDir, "音频")
}
func ensureExternalAssetDirectory(packagedAssets fs.FS, packagedRoot, targetDir, label string) (int, error) {
if targetDir == "" {
return 0, fmt.Errorf("%s目录不能为空", label)
}
if err := os.MkdirAll(targetDir, 0o755); err != nil {
return 0, fmt.Errorf("创建%s目录: %w", label, err)
}
extracted := 0
err := fs.WalkDir(packagedAssets, packagedRoot, func(assetPath string, entry fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if assetPath == packagedRoot {
return nil
}
relativePath := strings.TrimPrefix(assetPath, packagedRoot+"/")
if relativePath == assetPath || !fs.ValidPath(relativePath) {
return fmt.Errorf("无效的内置%s路径 %q", label, assetPath)
}
targetPath := filepath.Join(targetDir, filepath.FromSlash(relativePath))
relativeTarget, err := filepath.Rel(targetDir, targetPath)
if err != nil || relativeTarget == ".." || strings.HasPrefix(relativeTarget, ".."+string(filepath.Separator)) {
return fmt.Errorf("%s路径越界 %q", label, assetPath)
}
if entry.IsDir() {
if err := os.MkdirAll(targetPath, 0o755); err != nil {
return fmt.Errorf("创建%s子目录 %q: %w", label, relativePath, err)
}
return nil
}
created, err := copyEmbeddedFileIfMissing(packagedAssets, assetPath, targetPath)
if err != nil {
return fmt.Errorf("释放%s %q: %w", label, relativePath, err)
}
if created {
extracted++
}
return nil
})
if err != nil {
return extracted, err
}
return extracted, nil
}
func copyEmbeddedFileIfMissing(packagedAssets fs.FS, assetPath, targetPath string) (bool, error) {
if info, err := os.Lstat(targetPath); err == nil {
if info.IsDir() {
return false, fmt.Errorf("目标位置是目录")
}
return false, nil
} else if !os.IsNotExist(err) {
return false, err
}
if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil {
return false, err
}
source, err := packagedAssets.Open(assetPath)
if err != nil {
return false, err
}
defer source.Close()
temporary, err := os.CreateTemp(filepath.Dir(targetPath), ".lifegame-asset-*")
if err != nil {
return false, err
}
temporaryPath := temporary.Name()
defer os.Remove(temporaryPath)
if _, err := io.Copy(temporary, source); err != nil {
temporary.Close()
return false, err
}
if err := temporary.Chmod(0o644); err != nil {
temporary.Close()
return false, err
}
if err := temporary.Close(); err != nil {
return false, err
}
// 再检查一次,避免释放期间出现的用户文件被覆盖。
if _, err := os.Lstat(targetPath); err == nil {
return false, nil
} else if !os.IsNotExist(err) {
return false, err
}
if err := os.Rename(temporaryPath, targetPath); err != nil {
return false, err
}
return true, nil
}