├── LICENSE ├── README.md ├── filesystem ├── adapter │ ├── abstract.go │ ├── adapter.go │ └── local │ │ └── local.go ├── config │ └── config.go ├── directory.go ├── file.go ├── filesystem.go ├── filesystem_test.go ├── handler.go ├── interfaces │ ├── adapter.go │ └── config.go ├── mount_manager.go ├── testdata │ ├── test.txt │ ├── testcopy.txt │ └── testdir222 │ │ └── test.txt └── util │ └── util.go ├── go.mod └── logo.png /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020-3500 deatil(http://github.com/deatil) 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 文件管理器 2 | 3 | 4 | ### 项目介绍 5 | 6 | * go 版本实现的文件管理器 7 | 8 | 9 | ### 适配器 10 | 11 | * `local`: 本地存储 12 | 13 | 14 | ### 下载安装 15 | 16 | ~~~go 17 | go get -u github.com/deatil/go-filesystem 18 | ~~~ 19 | 20 | 21 | ### 示例 22 | 23 | ~~~go 24 | import ( 25 | "fmt" 26 | 27 | "github.com/deatil/go-filesystem/filesystem" 28 | local_adapter "github.com/deatil/go-filesystem/filesystem/adapter/local" 29 | ) 30 | 31 | func main() { 32 | // 根目录 33 | root := "/storage" 34 | adapter := local_adapter.New(root) 35 | 36 | // 磁盘 37 | fs := filesystem.New(adapter) 38 | 39 | // 写入数据 40 | path := "/path.txt" 41 | contents := []byte("testdata") 42 | 43 | ok, err := fs.Write(path, contents) 44 | if err != nil { 45 | fmt.Println(err.Error()) 46 | } 47 | } 48 | ~~~ 49 | 50 | 51 | ### 常用方法 52 | 53 | ~~~go 54 | // 写入 55 | Write(path, contents []byte) (bool, error) 56 | 57 | // 写入数据流 58 | WriteStream(path string, resource io.Reader) (bool, error) 59 | 60 | // 添加数据 61 | Put(path, contents []byte) (bool, error) 62 | 63 | // 添加数据流 64 | PutStream(path string, resource io.Reader) (bool, error) 65 | 66 | // 读取后删除 67 | ReadAndDelete(path string) (any, error) 68 | 69 | // 更新 70 | Update(path, contents []byte) (bool, error) 71 | 72 | // 读取 73 | Read(path string) ([]byte, error) 74 | 75 | // 重命名 76 | Rename(path, newpath string) (bool, error) 77 | 78 | // 复制 79 | Copy(path, newpath string) (bool, error) 80 | 81 | // 删除 82 | Delete(path string) (bool, error) 83 | 84 | // 删除文件夹 85 | DeleteDir(dirname string) (bool, error) 86 | 87 | // 创建文件夹 88 | CreateDir(dirname string) (bool, error) 89 | 90 | // 列出内容 91 | ListContents(dirname string) ([]map[string]any, error) 92 | ~~~ 93 | 94 | 95 | ### 开源协议 96 | 97 | * `go-filesystem` 遵循 `Apache2` 开源协议发布,在保留本软件版权的情况下提供个人及商业免费使用。 98 | 99 | 100 | ### 版权 101 | 102 | * 该系统所属版权归 deatil(https://github.com/deatil) 所有。 103 | -------------------------------------------------------------------------------- /filesystem/adapter/abstract.go: -------------------------------------------------------------------------------- 1 | package adapter 2 | 3 | import( 4 | "strings" 5 | ) 6 | 7 | /** 8 | * 通用基类 9 | * 10 | * @create 2021-8-1 11 | * @author deatil 12 | */ 13 | type Abstract struct { 14 | // 前缀 15 | pathPrefix string 16 | 17 | // 分割符 18 | pathSeparator string 19 | } 20 | 21 | // 设置前缀 22 | func (this *Abstract) SetPathPrefix(prefix string) { 23 | if prefix == "" { 24 | this.pathPrefix = "" 25 | 26 | return 27 | } 28 | 29 | this.pathSeparator = "/" 30 | this.pathPrefix = strings.TrimSuffix(prefix, "/") + this.pathSeparator 31 | } 32 | 33 | // 获取前缀 34 | func (this *Abstract) GetPathPrefix() string { 35 | return this.pathPrefix 36 | } 37 | 38 | // 添加前缀 39 | func (this *Abstract) ApplyPathPrefix(path string) string { 40 | return this.GetPathPrefix() + strings.TrimPrefix(path, "/") 41 | } 42 | 43 | // 移除前缀 44 | func (this *Abstract) RemovePathPrefix(path string) string { 45 | prefix := this.GetPathPrefix() 46 | return strings.TrimPrefix(path, prefix) 47 | } 48 | -------------------------------------------------------------------------------- /filesystem/adapter/adapter.go: -------------------------------------------------------------------------------- 1 | package adapter 2 | 3 | import( 4 | "io" 5 | 6 | "github.com/deatil/go-filesystem/filesystem/interfaces" 7 | ) 8 | 9 | /** 10 | * 空适配器 11 | * 12 | * @create 2021-8-1 13 | * @author deatil 14 | */ 15 | type Adapter struct { 16 | Abstract 17 | } 18 | 19 | // 判断 20 | func (this *Adapter) Has(string) bool { 21 | return false 22 | } 23 | 24 | // 上传 25 | func (this *Adapter) Write(path string, contents []byte, conf interfaces.Config) (map[string]any, error) { 26 | panic("go-filesystem: Write does not implement") 27 | } 28 | 29 | // 上传 Stream 文件类型 30 | func (this *Adapter) WriteStream(path string, stream io.Reader, conf interfaces.Config) (map[string]any, error) { 31 | panic("go-filesystem: WriteStream does not implement") 32 | } 33 | 34 | // 更新 35 | func (this *Adapter) Update(path string, contents []byte, conf interfaces.Config) (map[string]any, error) { 36 | panic("go-filesystem: Update does not implement") 37 | } 38 | 39 | // 更新 40 | func (this *Adapter) UpdateStream(path string, stream io.Reader, conf interfaces.Config) (map[string]any, error) { 41 | panic("go-filesystem: UpdateStream does not implement") 42 | } 43 | 44 | // 读取 45 | func (this *Adapter) Read(path string) (map[string]any, error) { 46 | panic("go-filesystem: Read does not implement") 47 | } 48 | 49 | // 读取数据为数据流 50 | func (this *Adapter) ReadStream(path string) (map[string]any, error) { 51 | panic("go-filesystem: ReadStream does not implement") 52 | } 53 | 54 | // 重命名 55 | func (this *Adapter) Rename(path string, newpath string) error { 56 | panic("go-filesystem: Rename does not implement") 57 | } 58 | 59 | // 复制 60 | func (this *Adapter) Copy(path string, newpath string) error { 61 | panic("go-filesystem: Copy does not implement") 62 | } 63 | 64 | // 删除 65 | func (this *Adapter) Delete(path string) error { 66 | panic("go-filesystem: Delete does not implement") 67 | } 68 | 69 | // 删除文件夹 70 | func (this *Adapter) DeleteDir(dirname string) error { 71 | panic("go-filesystem: DeleteDir does not implement") 72 | } 73 | 74 | // 创建文件夹 75 | func (this *Adapter) CreateDir(dirname string, conf interfaces.Config) (map[string]string, error) { 76 | panic("go-filesystem: CreateDir does not implement") 77 | } 78 | 79 | // 列出内容 80 | func (this *Adapter) ListContents(directory string, recursive ...bool) ([]map[string]any, error) { 81 | panic("go-filesystem: ListContents does not implement") 82 | } 83 | 84 | // 85 | func (this *Adapter) GetMetadata(path string) (map[string]any, error) { 86 | panic("go-filesystem: GetMetadata does not implement") 87 | } 88 | 89 | // 90 | func (this *Adapter) GetSize(path string) (map[string]any, error) { 91 | panic("go-filesystem: GetSize does not implement") 92 | } 93 | 94 | // 95 | func (this *Adapter) GetMimetype(path string) (map[string]any, error) { 96 | panic("go-filesystem: GetMimetype does not implement") 97 | } 98 | 99 | // 100 | func (this *Adapter) GetTimestamp(path string) (map[string]any, error) { 101 | panic("go-filesystem: GetTimestamp does not implement") 102 | } 103 | 104 | // 获取文件的权限 105 | func (this *Adapter) GetVisibility(path string) (map[string]string, error) { 106 | panic("go-filesystem: GetVisibility does not implement") 107 | } 108 | 109 | // 设置文件的权限 110 | func (this *Adapter) SetVisibility(path string, visibility string) (map[string]string, error) { 111 | panic("go-filesystem: SetVisibility does not implement") 112 | } 113 | -------------------------------------------------------------------------------- /filesystem/adapter/local/local.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "fmt" 7 | "errors" 8 | "strings" 9 | "net/http" 10 | "path/filepath" 11 | 12 | "github.com/deatil/go-filesystem/filesystem/adapter" 13 | "github.com/deatil/go-filesystem/filesystem/interfaces" 14 | ) 15 | 16 | // 权限列表 17 | var permissionMap map[string]map[string]uint32 = map[string]map[string]uint32{ 18 | "file": { 19 | "public": 0644, 20 | "private": 0600, 21 | }, 22 | "dir": { 23 | "public": 0755, 24 | "private": 0700, 25 | }, 26 | } 27 | 28 | /** 29 | * 本地文件适配器 / Local adapter 30 | * 31 | * @create 2021-8-1 32 | * @author deatil 33 | */ 34 | type Local struct { 35 | // 默认适配器基类 36 | adapter.Adapter 37 | 38 | // 权限 39 | visibility string 40 | } 41 | 42 | // 本地文件适配器 43 | func New(root string) *Local { 44 | local := &Local{} 45 | 46 | local.EnsureDirectory(root) 47 | local.SetPathPrefix(root) 48 | 49 | return local 50 | } 51 | 52 | // 确认文件夹 53 | func (this *Local) EnsureDirectory(root string) error { 54 | err := os.MkdirAll(root, this.formatPerm(permissionMap["dir"]["public"])) 55 | if err != nil { 56 | return errors.New("go-filesystem: exec os.MkdirAll() fail, error: " + err.Error()) 57 | } 58 | 59 | if !this.isFile(root) { 60 | return errors.New("go-filesystem: create dir fail" ) 61 | } 62 | 63 | return nil 64 | } 65 | 66 | // 判断是否存在 67 | func (this *Local) Has(path string) bool { 68 | location := this.ApplyPathPrefix(path) 69 | 70 | _, err := os.Stat(location) 71 | return err == nil || os.IsExist(err) 72 | } 73 | 74 | // 上传 75 | func (this *Local) Write(path string, contents []byte, conf interfaces.Config) (map[string]any, error) { 76 | location := this.ApplyPathPrefix(path) 77 | this.EnsureDirectory(filepath.Dir(location)) 78 | 79 | out, createErr := os.Create(location) 80 | if createErr != nil { 81 | return nil, errors.New("go-filesystem: exec os.Create() fail, error: " + createErr.Error()) 82 | } 83 | 84 | defer out.Close() 85 | 86 | _, writeErr := out.Write(contents) 87 | if writeErr != nil { 88 | return nil, errors.New("go-filesystem: exec os.Write() fail, error: " + writeErr.Error()) 89 | } 90 | 91 | size, sizeErr := this.fileSize(location) 92 | if sizeErr != nil { 93 | return nil, errors.New("go-filesystem: get file size fail, error: " + writeErr.Error()) 94 | } 95 | 96 | result := map[string]any{ 97 | "type": "file", 98 | "size": size, 99 | "path": path, 100 | "contents": contents, 101 | } 102 | 103 | if visibility := conf.Get("visibility"); visibility != nil { 104 | result["visibility"] = visibility.(string) 105 | this.SetVisibility(location, visibility.(string)) 106 | } 107 | 108 | return result, nil 109 | } 110 | 111 | // 上传 Stream 文件类型 112 | func (this *Local) WriteStream(path string, stream io.Reader, conf interfaces.Config) (map[string]any, error) { 113 | location := this.ApplyPathPrefix(path) 114 | this.EnsureDirectory(filepath.Dir(location)) 115 | 116 | newFile, createErr := os.Create(location) 117 | if createErr != nil { 118 | return nil, errors.New("go-filesystem: exec os.Create() fail, error: " + createErr.Error()) 119 | } 120 | 121 | defer newFile.Close() 122 | 123 | _, copyErr := io.Copy(newFile, stream) 124 | if copyErr != nil { 125 | return nil, errors.New("go-filesystem: write stream fail, error: " + copyErr.Error()) 126 | } 127 | 128 | result := map[string]any{ 129 | "type": "file", 130 | "path": path, 131 | } 132 | 133 | if visibility := conf.Get("visibility"); visibility != nil { 134 | result["visibility"] = visibility.(string) 135 | this.SetVisibility(location, visibility.(string)) 136 | } 137 | 138 | return result, nil 139 | } 140 | 141 | // 更新 142 | func (this *Local) Update(path string, contents []byte, conf interfaces.Config) (map[string]any, error) { 143 | location := this.ApplyPathPrefix(path) 144 | 145 | out, createErr := os.Create(location) 146 | if createErr != nil { 147 | return nil, errors.New("go-filesystem: exec os.Create() fail, error: " + createErr.Error()) 148 | } 149 | 150 | defer out.Close() 151 | 152 | _, writeErr := out.Write(contents) 153 | if writeErr != nil { 154 | return nil, errors.New("go-filesystem: exec os.Write() fail, error: " + writeErr.Error()) 155 | } 156 | 157 | size, sizeErr := this.fileSize(location) 158 | if sizeErr != nil { 159 | return nil, errors.New("go-filesystem: get file size fail, error: " + writeErr.Error()) 160 | } 161 | 162 | result := map[string]any{ 163 | "type": "file", 164 | "size": size, 165 | "path": path, 166 | "contents": contents, 167 | } 168 | 169 | if visibility := conf.Get("visibility"); visibility != nil { 170 | result["visibility"] = visibility.(string) 171 | this.SetVisibility(location, visibility.(string)) 172 | } 173 | 174 | return result, nil 175 | } 176 | 177 | // 更新 178 | func (this *Local) UpdateStream(path string, stream io.Reader, config interfaces.Config) (map[string]any, error) { 179 | return this.WriteStream(path, stream, config) 180 | } 181 | 182 | // 读取 183 | func (this *Local) Read(path string) (map[string]any, error) { 184 | location := this.ApplyPathPrefix(path) 185 | 186 | file, err := os.Open(location) 187 | if err != nil { 188 | return nil, errors.New("go-filesystem: exec os.Open() fail, error: " + err.Error()) 189 | } 190 | defer file.Close() 191 | 192 | contents, err := io.ReadAll(file) 193 | if err != nil { 194 | return nil, errors.New("go-filesystem: exec io.ReadAll() fail, error: " + err.Error()) 195 | } 196 | 197 | return map[string]any{ 198 | "type": "file", 199 | "path": path, 200 | "contents": contents, 201 | }, nil 202 | } 203 | 204 | // 读取成文件流 205 | // 打开文件需要手动关闭 206 | func (this *Local) ReadStream(path string) (map[string]any, error) { 207 | location := this.ApplyPathPrefix(path) 208 | 209 | stream, err := os.Open(location) 210 | if err != nil { 211 | return nil, errors.New("go-filesystem: exec os.Open() fail, error: " + err.Error()) 212 | } 213 | 214 | // defer stream.Close() 215 | 216 | return map[string]any{ 217 | "type": "file", 218 | "path": path, 219 | "stream": stream, 220 | }, nil 221 | } 222 | 223 | // 重命名 224 | func (this *Local) Rename(path string, newpath string) error { 225 | location := this.ApplyPathPrefix(path) 226 | destination := this.ApplyPathPrefix(newpath) 227 | parentDirectory := this.ApplyPathPrefix(filepath.Dir(newpath)) 228 | 229 | this.EnsureDirectory(parentDirectory) 230 | 231 | err := os.Rename(location, destination) 232 | if err != nil { 233 | return errors.New("go-filesystem: exec os.Rename() fail, error: " + err.Error()) 234 | } 235 | 236 | return nil 237 | } 238 | 239 | // 复制 240 | func (this *Local) Copy(path string, newpath string) error { 241 | location := this.ApplyPathPrefix(path) 242 | destination := this.ApplyPathPrefix(newpath) 243 | 244 | this.EnsureDirectory(filepath.Dir(destination)) 245 | 246 | locationStat, e := os.Stat(location) 247 | if e != nil { 248 | return e 249 | } 250 | 251 | if !locationStat.Mode().IsRegular() { 252 | return fmt.Errorf("go-filesystem: %s not right file", path) 253 | } 254 | 255 | src, err := os.Open(location) 256 | if err != nil { 257 | return err 258 | } 259 | defer src.Close() 260 | 261 | dst, err := os.Create(destination) 262 | if err != nil { 263 | return err 264 | } 265 | defer dst.Close() 266 | 267 | _, err = io.Copy(dst, src) 268 | if err != nil { 269 | return errors.New("go-filesystem: copy fail, error: " + err.Error()) 270 | } 271 | 272 | return nil 273 | } 274 | 275 | // 删除 276 | func (this *Local) Delete(path string) error { 277 | location := this.ApplyPathPrefix(path) 278 | 279 | if !this.isFile(location) { 280 | return errors.New("go-filesystem: file delete fail, not file type") 281 | } 282 | 283 | if err := os.Remove(location); err != nil { 284 | return errors.New("go-filesystem: file delete fail, error: " + err.Error()) 285 | } 286 | 287 | return nil 288 | } 289 | 290 | // 删除文件夹 291 | func (this *Local) DeleteDir(dirname string) error { 292 | location := this.ApplyPathPrefix(dirname) 293 | 294 | if !this.isDir(location) { 295 | return errors.New("go-filesystem: file delete fail, not file type") 296 | } 297 | 298 | if err := os.RemoveAll(location); err != nil { 299 | return errors.New("go-filesystem: file delete fail, error: " + err.Error()) 300 | } 301 | 302 | return nil 303 | } 304 | 305 | // 创建文件夹 306 | func (this *Local) CreateDir(dirname string, config interfaces.Config) (map[string]string, error) { 307 | location := this.ApplyPathPrefix(dirname) 308 | 309 | visibility := config.Get("visibility", "public").(string) 310 | 311 | err := os.MkdirAll(location, this.formatPerm(permissionMap["dir"][visibility])) 312 | if err != nil { 313 | return nil, errors.New("go-filesystem: exec os.MkdirAll() fail, error: " + err.Error()) 314 | } 315 | 316 | if !this.isDir(location) { 317 | return nil, errors.New("go-filesystem: make dir fail") 318 | } 319 | 320 | data := map[string]string{ 321 | "path": dirname, 322 | "type": "dir", 323 | } 324 | 325 | return data, nil 326 | } 327 | 328 | // 列出内容 329 | func (this *Local) ListContents(directory string, recursive ...bool) ([]map[string]any, error) { 330 | location := this.ApplyPathPrefix(directory) 331 | 332 | if !this.isDir(location) { 333 | return []map[string]any{}, nil 334 | } 335 | 336 | var iterator []map[string]any 337 | if len(recursive) > 0 && recursive[0] { 338 | iterator, _ = this.getRecursiveDirectoryIterator(location) 339 | } else { 340 | iterator, _ = this.getDirectoryIterator(location) 341 | } 342 | 343 | var result []map[string]any 344 | for _, file := range iterator { 345 | path, _ := this.normalizeFileInfo(file) 346 | 347 | result = append(result, path) 348 | } 349 | 350 | return result, nil 351 | } 352 | 353 | func (this *Local) GetMetadata(path string) (map[string]any, error) { 354 | location := this.ApplyPathPrefix(path) 355 | 356 | info := this.fileInfo(location) 357 | 358 | return this.normalizeFileInfo(info) 359 | } 360 | 361 | func (this *Local) GetSize(path string) (map[string]any, error) { 362 | return this.GetMetadata(path) 363 | } 364 | 365 | func (this *Local) GetMimetype(path string) (map[string]any, error) { 366 | location := this.ApplyPathPrefix(path) 367 | 368 | f, err := os.Open(location) 369 | if err != nil { 370 | return nil, err 371 | } 372 | defer f.Close() 373 | 374 | // 头部字节 375 | buffer := make([]byte, 32) 376 | if _, err := f.Read(buffer); err != nil { 377 | return nil, err 378 | } 379 | 380 | mimetype := http.DetectContentType(buffer) 381 | 382 | return map[string]any{ 383 | "path": path, 384 | "type": "file", 385 | "mimetype": mimetype, 386 | }, nil 387 | } 388 | 389 | func (this *Local) GetTimestamp(path string) (map[string]any, error) { 390 | return this.GetMetadata(path) 391 | } 392 | 393 | // 设置文件的权限 394 | func (this *Local) GetVisibility(path string) (map[string]string, error) { 395 | location := this.ApplyPathPrefix(path) 396 | 397 | pathType := "file" 398 | if !this.isFile(location) { 399 | pathType = "dir" 400 | } 401 | 402 | permissions, _ := this.fileMode(location) 403 | 404 | for visibility, visibilityPermissions := range permissionMap[pathType] { 405 | if visibilityPermissions == permissions { 406 | return map[string]string{ 407 | "path": path, 408 | "visibility": visibility, 409 | }, nil 410 | } 411 | } 412 | 413 | permission := fmt.Sprintf("%o", permissions) 414 | 415 | data := map[string]string{ 416 | "path": path, 417 | "visibility": permission, 418 | } 419 | 420 | return data, nil 421 | } 422 | 423 | // 设置文件的权限 424 | func (this *Local) SetVisibility(path string, visibility string) (map[string]string, error) { 425 | location := this.ApplyPathPrefix(path) 426 | 427 | pathType := "file" 428 | if !this.isFile(location) { 429 | pathType = "dir" 430 | } 431 | 432 | if visibility != "private" { 433 | visibility = "public" 434 | } 435 | 436 | e := os.Chmod(location, this.formatPerm(permissionMap[pathType][visibility])) 437 | if e != nil { 438 | return nil, errors.New("go-filesystem: set permission fail") 439 | } 440 | 441 | data := map[string]string{ 442 | "path": path, 443 | "visibility": visibility, 444 | } 445 | 446 | return data, nil 447 | } 448 | 449 | // normalizeFileInfo 450 | func (this *Local) normalizeFileInfo(file map[string]any) (map[string]any, error) { 451 | return this.mapFileInfo(file) 452 | } 453 | 454 | // 获取全部文件 455 | func (this *Local) getRecursiveDirectoryIterator(path string) ([]map[string]any, error) { 456 | var files []map[string]any 457 | err := filepath.Walk(path, func(wpath string, info os.FileInfo, err error) error { 458 | var fileType string 459 | if info.IsDir() { 460 | fileType = "dir" 461 | } else { 462 | fileType = "file" 463 | } 464 | 465 | files = append(files, map[string]any{ 466 | "type": fileType, 467 | "path": path, 468 | "filename": info.Name(), 469 | "pathname": path + "/" + info.Name(), 470 | "timestamp": info.ModTime().Unix(), 471 | "info": info, 472 | }) 473 | return nil 474 | }) 475 | 476 | if err != nil { 477 | return nil, errors.New("go-filesystem: get dir list fail") 478 | } 479 | 480 | return files, nil 481 | } 482 | 483 | // 一级目录索引 484 | // dir index 485 | func (this *Local) getDirectoryIterator(path string) ([]map[string]any, error) { 486 | fs, err := os.ReadDir(path) 487 | if err != nil { 488 | return []map[string]any{}, err 489 | } 490 | 491 | sz := len(fs) 492 | if sz == 0 { 493 | return []map[string]any{}, nil 494 | } 495 | 496 | ret := make([]map[string]any, 0, sz) 497 | for i := 0; i < sz; i++ { 498 | info := fs[i] 499 | 500 | name := info.Name() 501 | 502 | // type := info.Type() 503 | stat, _ := info.Info() 504 | 505 | if name != "." && name != ".." { 506 | var fileType string 507 | if info.IsDir() { 508 | fileType = "dir" 509 | } else { 510 | fileType = "file" 511 | } 512 | 513 | ret = append(ret, map[string]any{ 514 | "type": fileType, 515 | "path": path, 516 | "filename": name, 517 | "pathname": path + "/" + name, 518 | "timestamp": stat.ModTime().Unix(), 519 | "info": info, 520 | }) 521 | } 522 | } 523 | 524 | return ret, nil 525 | } 526 | 527 | func (this *Local) fileInfo(path string) map[string]any { 528 | info, e := os.Stat(path) 529 | if e != nil { 530 | return nil 531 | } 532 | 533 | var fileType string 534 | if info.IsDir() { 535 | fileType = "dir" 536 | } else { 537 | fileType = "file" 538 | } 539 | 540 | return map[string]any{ 541 | "type": fileType, 542 | "path": filepath.Dir(path), 543 | "filename": info.Name(), 544 | "pathname": path, 545 | "timestamp": info.ModTime().Unix(), 546 | "info": info, 547 | } 548 | } 549 | 550 | func (this *Local) getFilePath(file map[string]any) string { 551 | location := file["pathname"].(string) 552 | path := this.RemovePathPrefix(location) 553 | return strings.Trim(strings.Replace(path, "\\", "/", -1), "/") 554 | } 555 | 556 | // 获取全部文件 557 | // get all file 558 | func (this *Local) mapFileInfo(data map[string]any) (map[string]any, error) { 559 | normalized := map[string]any{ 560 | "type": data["type"], 561 | "path": this.getFilePath(data), 562 | "timestamp": data["timestamp"], 563 | } 564 | 565 | if data["type"] == "file" { 566 | switch infoType := data["info"].(type) { 567 | case os.DirEntry: 568 | info, err := infoType.Info() 569 | if err == nil { 570 | normalized["size"] = info.Size() 571 | } else { 572 | normalized["size"] = 0 573 | } 574 | case os.FileInfo: 575 | normalized["size"] = infoType.Size() 576 | } 577 | } 578 | 579 | return normalized, nil 580 | } 581 | 582 | func (this *Local) isFile(fp string) bool { 583 | return !this.isDir(fp) 584 | } 585 | 586 | func (this *Local) isDir(fp string) bool { 587 | f, e := os.Stat(fp) 588 | if e != nil { 589 | return false 590 | } 591 | 592 | return f.IsDir() 593 | } 594 | 595 | func (this *Local) fileSize(fp string) (int64, error) { 596 | f, e := os.Stat(fp) 597 | if e != nil { 598 | return 0, e 599 | } 600 | 601 | return f.Size(), nil 602 | } 603 | 604 | // 文件权限 605 | // return File Mode 606 | func (this *Local) fileMode(fp string) (uint32, error) { 607 | f, e := os.Stat(fp) 608 | if e != nil { 609 | return 0, e 610 | } 611 | 612 | perm := f.Mode().Perm() 613 | 614 | return uint32(perm), nil 615 | } 616 | 617 | // 权限格式化 618 | // Format Perm 619 | func (this *Local) formatPerm(i uint32) os.FileMode { 620 | // 八进制转成十进制 621 | // p, _ := strconv.ParseInt(strconv.Itoa(i), 8, 0) 622 | return os.FileMode(i) 623 | } 624 | -------------------------------------------------------------------------------- /filesystem/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import( 4 | "github.com/deatil/go-filesystem/filesystem/interfaces" 5 | ) 6 | 7 | /** 8 | * 配置 9 | * 10 | * @create 2021-8-1 11 | * @author deatil 12 | */ 13 | type Config struct { 14 | // 数据 15 | data map[string]any 16 | } 17 | 18 | /** 19 | * 构造函数 20 | */ 21 | func New(data map[string]any) Config { 22 | return Config{ 23 | data: data, 24 | } 25 | } 26 | 27 | /** 28 | * 覆盖旧数据 29 | */ 30 | func (this Config) With(data map[string]any) interfaces.Config { 31 | this.data = data 32 | 33 | return this 34 | } 35 | 36 | /** 37 | * 设置单个新数据 38 | */ 39 | func (this Config) Set(key string, value any) interfaces.Config { 40 | this.data[key] = value 41 | 42 | return this 43 | } 44 | 45 | /** 46 | * 是否存在 47 | */ 48 | func (this Config) Has(key string) bool { 49 | if _, ok := this.data[key]; ok { 50 | return true 51 | } 52 | 53 | return false 54 | } 55 | 56 | /** 57 | * 获取一个带默认的值 58 | */ 59 | func (this Config) Get(key string, defaults ...any) any { 60 | if data, ok := this.data[key]; ok { 61 | return data 62 | } 63 | 64 | if len(defaults) > 0 { 65 | return defaults[0] 66 | } 67 | 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /filesystem/directory.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | /** 4 | * 文件管理器文件夹操作扩展 5 | * 6 | * @create 2021-8-1 7 | * @author deatil 8 | */ 9 | type Directory struct { 10 | Handler 11 | } 12 | 13 | // new 文件管理器 14 | func NewDirectory(filesystem *Filesystem, path ...string) *Directory { 15 | fs := &Directory{} 16 | fs.filesystem = filesystem 17 | 18 | if len(path) > 0{ 19 | fs.path = path[0] 20 | } 21 | 22 | return fs 23 | } 24 | 25 | // 设置管理器 26 | func (this *Directory) WithFilesystem(filesystem *Filesystem) *Directory { 27 | this.filesystem = filesystem 28 | 29 | return this 30 | } 31 | 32 | // 设置目录 33 | func (this *Directory) WithPath(path string) *Directory { 34 | this.path = path 35 | 36 | return this 37 | } 38 | 39 | // 删除文件夹 40 | func (this *Directory) Delete() (bool, error) { 41 | return this.filesystem.DeleteDir(this.path) 42 | } 43 | 44 | // 列出文件 45 | func (this *Directory) GetContents(recursive ...bool) ([]map[string]any, error) { 46 | return this.filesystem.ListContents(this.path, recursive...) 47 | } 48 | -------------------------------------------------------------------------------- /filesystem/file.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | import( 4 | "io" 5 | "os" 6 | ) 7 | 8 | /** 9 | * 文件管理扩展 10 | * 11 | * @create 2021-8-1 12 | * @author deatil 13 | */ 14 | type File struct { 15 | Handler 16 | } 17 | 18 | // new 文件管理器 19 | func NewFile(filesystem *Filesystem, path ...string) *File { 20 | fs := &File{} 21 | fs.filesystem = filesystem 22 | 23 | if len(path) > 0{ 24 | fs.path = path[0] 25 | } 26 | 27 | return fs 28 | } 29 | 30 | // 设置管理器 31 | func (this *File) WithFilesystem(filesystem *Filesystem) *File { 32 | this.filesystem = filesystem 33 | 34 | return this 35 | } 36 | 37 | // 设置目录 38 | func (this *File) WithPath(path string) *File { 39 | this.path = path 40 | 41 | return this 42 | } 43 | 44 | // 存在 45 | func (this *File) Exists() bool { 46 | return this.filesystem.Has(this.path) 47 | } 48 | 49 | // 读取 50 | func (this *File) Read() ([]byte, error) { 51 | return this.filesystem.Read(this.path) 52 | } 53 | 54 | // 读取成文件流 55 | func (this *File) ReadStream() (*os.File, error) { 56 | return this.filesystem.ReadStream(this.path) 57 | } 58 | 59 | // 写入字节 60 | func (this *File) Write(content []byte) (bool, error) { 61 | return this.filesystem.Write(this.path, content) 62 | } 63 | 64 | // 写入文件流 65 | func (this *File) WriteStream(resource io.Reader) (bool, error) { 66 | return this.filesystem.WriteStream(this.path, resource) 67 | } 68 | 69 | // 更新字节 70 | func (this *File) Update(content []byte) (bool, error) { 71 | return this.filesystem.Update(this.path, content) 72 | } 73 | 74 | // 更新文件流 75 | func (this *File) UpdateStream(resource io.Reader) (bool, error) { 76 | return this.filesystem.UpdateStream(this.path, resource) 77 | } 78 | 79 | // 导入字节 80 | func (this *File) Put(content []byte) (bool, error) { 81 | return this.filesystem.Update(this.path, content) 82 | } 83 | 84 | // 导入文件流 85 | func (this *File) PutStream(resource *os.File) (bool, error) { 86 | return this.filesystem.PutStream(this.path, resource) 87 | } 88 | 89 | // 重命名 90 | func (this *File) Rename(newpath string) (bool, error) { 91 | if _, err := this.filesystem.Rename(this.path, newpath); err != nil { 92 | return false, err 93 | } 94 | 95 | this.path = newpath 96 | return true, nil 97 | } 98 | 99 | // 复制 100 | func (this *File) Copy(newpath string) (*File, error) { 101 | _, err := this.filesystem.Copy(this.path, newpath) 102 | if err == nil { 103 | var file2 = &File{} 104 | file2.filesystem = this.filesystem 105 | file2.path = newpath 106 | 107 | return file2, nil 108 | } 109 | 110 | return nil, err 111 | } 112 | 113 | // 删除 114 | func (this *File) Delete() (bool, error) { 115 | return this.filesystem.Delete(this.path) 116 | } 117 | 118 | // 时间戳 119 | func (this *File) GetTimestamp() (int64, error) { 120 | return this.filesystem.GetTimestamp(this.path) 121 | } 122 | 123 | // 文件类型 124 | func (this *File) GetMimetype() (string, error) { 125 | return this.filesystem.GetMimetype(this.path) 126 | } 127 | 128 | // 权限 129 | func (this *File) GetVisibility() (string, error) { 130 | return this.filesystem.GetVisibility(this.path) 131 | } 132 | 133 | // 数据 134 | func (this *File) GetMetadata() (map[string]any, error) { 135 | return this.filesystem.GetMetadata(this.path) 136 | } 137 | 138 | // 大小 139 | func (this *File) GetSize() (int64, error) { 140 | return this.filesystem.GetSize(this.path) 141 | } 142 | -------------------------------------------------------------------------------- /filesystem/filesystem.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | import( 4 | "io" 5 | "os" 6 | "bytes" 7 | "errors" 8 | 9 | "github.com/deatil/go-filesystem/filesystem/util" 10 | "github.com/deatil/go-filesystem/filesystem/config" 11 | "github.com/deatil/go-filesystem/filesystem/interfaces" 12 | ) 13 | 14 | /** 15 | * 文件管理器 16 | * Filesystem struct 17 | * 18 | * @create 2021-8-1 19 | * @author deatil 20 | */ 21 | type Filesystem struct { 22 | // 适配器 / Adapter 23 | adapter interfaces.Adapter 24 | 25 | // 配置 / Config 26 | config interfaces.Config 27 | } 28 | 29 | // 文件管理器 30 | // return a *Filesystem 31 | func New(adapter interfaces.Adapter, conf ...map[string]any) *Filesystem { 32 | fs := &Filesystem{ 33 | adapter: adapter, 34 | } 35 | 36 | if len(conf) > 0{ 37 | fs.config = fs.PrepareConfig(conf[0]) 38 | } 39 | 40 | return fs 41 | } 42 | 43 | // 设置配置 44 | // With Config 45 | func (this *Filesystem) WithConfig(conf interfaces.Config) { 46 | this.config = conf 47 | } 48 | 49 | // 获取配置 50 | // Get Config 51 | func (this *Filesystem) GetConfig() interfaces.Config { 52 | return this.config 53 | } 54 | 55 | // 提前设置配置 56 | // Prepare Config 57 | func (this *Filesystem) PrepareConfig(settings map[string]any) interfaces.Config { 58 | conf := config.New(settings) 59 | 60 | return conf 61 | } 62 | 63 | // 设置适配器 64 | // With Adapter 65 | func (this *Filesystem) WithAdapter(adapters interfaces.Adapter) *Filesystem { 66 | this.adapter = adapters 67 | return this 68 | } 69 | 70 | // 获取适配器 71 | // Get Adapter 72 | func (this *Filesystem) GetAdapter() interfaces.Adapter { 73 | return this.adapter 74 | } 75 | 76 | // 判断 77 | // return true if path exists, else false 78 | func (this *Filesystem) Has(path string) bool { 79 | path = util.NormalizePath(path) 80 | 81 | if len(path) == 0 { 82 | return false 83 | } 84 | 85 | return this.adapter.Has(path) 86 | } 87 | 88 | // 写入文件 89 | // Write contents to path 90 | func (this *Filesystem) Write(path string, contents []byte, conf ...map[string]any) (bool, error) { 91 | path = util.NormalizePath(path) 92 | 93 | var newConf map[string]any 94 | if len(conf) > 0 { 95 | newConf = conf[0] 96 | } 97 | 98 | configs := this.PrepareConfig(newConf) 99 | 100 | if _, err := this.adapter.Write(path, contents, configs); err != nil { 101 | return false, err 102 | } 103 | 104 | return true, nil 105 | } 106 | 107 | // 写入数据流 108 | // Write stream resource to path 109 | func (this *Filesystem) WriteStream(path string, resource io.Reader, conf ...map[string]any) (bool, error) { 110 | path = util.NormalizePath(path) 111 | 112 | var newConf map[string]any 113 | if len(conf) > 0 { 114 | newConf = conf[0] 115 | } 116 | 117 | configs := this.PrepareConfig(newConf) 118 | 119 | if _, err := this.adapter.WriteStream(path, resource, configs); err != nil { 120 | return false, err 121 | } 122 | 123 | return true, nil 124 | } 125 | 126 | // 更新 127 | // Put contents to path 128 | func (this *Filesystem) Put(path string, contents []byte, conf ...map[string]any) (bool, error) { 129 | path = util.NormalizePath(path) 130 | 131 | var newConf map[string]any 132 | if len(conf) > 0 { 133 | newConf = conf[0] 134 | } 135 | 136 | configs := this.PrepareConfig(newConf) 137 | 138 | if this.Has(path) { 139 | if _, err := this.adapter.Update(path, contents, configs); err != nil { 140 | return false, err 141 | } 142 | 143 | return true, nil 144 | } 145 | 146 | if _, err := this.adapter.Write(path, contents, configs); err != nil { 147 | return false, err 148 | } 149 | 150 | return true, nil 151 | } 152 | 153 | // 更新数据流 154 | // Put stream resource to path 155 | func (this *Filesystem) PutStream(path string, resource io.Reader, conf ...map[string]any) (bool, error) { 156 | path = util.NormalizePath(path) 157 | 158 | var newConf map[string]any 159 | if len(conf) > 0 { 160 | newConf = conf[0] 161 | } 162 | 163 | configs := this.PrepareConfig(newConf) 164 | 165 | if this.Has(path) { 166 | if _, err := this.adapter.UpdateStream(path, resource, configs); err != nil { 167 | return false, err 168 | } 169 | 170 | return true, nil 171 | } 172 | 173 | if _, err := this.adapter.WriteStream(path, resource, configs); err != nil { 174 | return false, err 175 | } 176 | 177 | return true, nil 178 | } 179 | 180 | // 读取并删除 181 | // read and delete 182 | func (this *Filesystem) ReadAndDelete(path string) ([]byte, error) { 183 | path = util.NormalizePath(path) 184 | 185 | contents, err := this.Read(path) 186 | if err != nil { 187 | return nil, err 188 | } 189 | 190 | this.Delete(path) 191 | 192 | return contents, nil 193 | } 194 | 195 | // 更新字符 196 | // Update 197 | func (this *Filesystem) Update(path string, contents []byte, conf ...map[string]any) (bool, error) { 198 | path = util.NormalizePath(path) 199 | 200 | var newConf map[string]any 201 | if len(conf) > 0 { 202 | newConf = conf[0] 203 | } 204 | 205 | configs := this.PrepareConfig(newConf) 206 | 207 | if _, err := this.adapter.Update(path, contents, configs); err != nil { 208 | return false, err 209 | } 210 | 211 | return true, nil 212 | } 213 | 214 | // 更新数据流 215 | // Update Stream 216 | func (this *Filesystem) UpdateStream(path string, resource io.Reader, conf ...map[string]any) (bool, error) { 217 | path = util.NormalizePath(path) 218 | 219 | var newConf map[string]any 220 | if len(conf) > 0 { 221 | newConf = conf[0] 222 | } 223 | 224 | configs := this.PrepareConfig(newConf) 225 | 226 | if _, err := this.adapter.WriteStream(path, resource, configs); err != nil { 227 | return false, err 228 | } 229 | 230 | return true, nil 231 | } 232 | 233 | // 文件头添加 234 | // Prepend contents 235 | func (this *Filesystem) Prepend(path string, contents []byte, conf ...map[string]any) (bool, error) { 236 | if this.Has(path) { 237 | data, err := this.Read(path) 238 | if err != nil { 239 | return false, err 240 | } 241 | 242 | return this.Put(path, append(contents, data...), conf...) 243 | } 244 | 245 | return this.Put(path, contents, conf...) 246 | } 247 | 248 | // 文件头添加数据流 249 | // Prepend resource Stream 250 | func (this *Filesystem) PrependStream(path string, resource io.Reader, conf ...map[string]any) (bool, error) { 251 | if this.Has(path) { 252 | data, err := this.Read(path) 253 | if err != nil { 254 | return false, err 255 | } 256 | 257 | buf := &bytes.Buffer{} 258 | 259 | _, err = io.Copy(buf, resource) 260 | if err != nil { 261 | return false, errors.New("go-filesystem: read resource fail, error: " + err.Error()) 262 | } 263 | 264 | buf.Write(data) 265 | 266 | return this.PutStream(path, buf, conf...) 267 | } 268 | 269 | return this.PutStream(path, resource, conf...) 270 | } 271 | 272 | // 尾部添加 273 | // Append contents 274 | func (this *Filesystem) Append(path string, contents []byte, conf ...map[string]any) (bool, error) { 275 | if this.Has(path) { 276 | data, err := this.Read(path) 277 | if err != nil { 278 | return false, err 279 | } 280 | 281 | return this.Put(path, append(data, contents...), conf...) 282 | } 283 | 284 | return this.Put(path, contents, conf...) 285 | } 286 | 287 | // 尾部添加数据流 288 | // Append resource Stream 289 | func (this *Filesystem) AppendStream(path string, resource io.Reader, conf ...map[string]any) (bool, error) { 290 | if this.Has(path) { 291 | data, err := this.Read(path) 292 | if err != nil { 293 | return false, err 294 | } 295 | 296 | buf := &bytes.Buffer{} 297 | buf.Write(data) 298 | 299 | _, err = io.Copy(buf, resource) 300 | if err != nil { 301 | return false, errors.New("go-filesystem: read resource fail, error: " + err.Error()) 302 | } 303 | 304 | return this.PutStream(path, buf, conf...) 305 | } 306 | 307 | return this.PutStream(path, resource, conf...) 308 | } 309 | 310 | // 文件到字符 311 | // Read bytes 312 | func (this *Filesystem) Read(path string) ([]byte, error) { 313 | path = util.NormalizePath(path) 314 | object, err := this.adapter.Read(path) 315 | 316 | if err != nil { 317 | return nil, err 318 | } 319 | 320 | return object["contents"].([]byte), nil 321 | } 322 | 323 | // 读取成数据流 324 | // Read and return Stream 325 | func (this *Filesystem) ReadStream(path string) (*os.File, error) { 326 | path = util.NormalizePath(path) 327 | object, err := this.adapter.ReadStream(path) 328 | 329 | if err != nil { 330 | return nil, err 331 | } 332 | 333 | return object["stream"].(*os.File), nil 334 | } 335 | 336 | // 重命名 337 | // Rename path 338 | func (this *Filesystem) Rename(path string, newpath string) (bool, error) { 339 | path = util.NormalizePath(path) 340 | newpath = util.NormalizePath(newpath) 341 | 342 | if err := this.adapter.Rename(path, newpath); err != nil { 343 | return false, err 344 | } 345 | 346 | return true, nil 347 | } 348 | 349 | // 复制 350 | // Copy path 351 | func (this *Filesystem) Copy(path string, newpath string) (bool, error) { 352 | path = util.NormalizePath(path) 353 | newpath = util.NormalizePath(newpath) 354 | 355 | if err := this.adapter.Copy(path, newpath); err != nil { 356 | return false, err 357 | } 358 | 359 | return true, nil 360 | } 361 | 362 | // 删除 363 | // Delete path 364 | func (this *Filesystem) Delete(path string) (bool, error) { 365 | path = util.NormalizePath(path) 366 | 367 | if err := this.adapter.Delete(path); err != nil { 368 | return false, err 369 | } 370 | 371 | return true, nil 372 | } 373 | 374 | // 删除文件夹 375 | // Delete Dir 376 | func (this *Filesystem) DeleteDir(dirname string) (bool, error) { 377 | dirname = util.NormalizePath(dirname) 378 | if dirname == "" { 379 | return false, errors.New("文件夹路径错误") 380 | } 381 | 382 | if err := this.adapter.DeleteDir(dirname); err != nil { 383 | return false, err 384 | } 385 | 386 | return true, nil 387 | } 388 | 389 | // 创建文件夹 390 | // Create Dir 391 | func (this *Filesystem) CreateDir(dirname string, conf ...map[string]any) (bool, error) { 392 | dirname = util.NormalizePath(dirname) 393 | 394 | var newConf map[string]any 395 | if len(conf) > 0 { 396 | newConf = conf[0] 397 | } 398 | 399 | configs := this.PrepareConfig(newConf) 400 | 401 | if _, err := this.adapter.CreateDir(dirname, configs); err != nil { 402 | return false, err 403 | } 404 | 405 | return true, nil 406 | } 407 | 408 | // 列表 409 | // ListContents 410 | func (this *Filesystem) ListContents(dirname string, recursive ...bool) ([]map[string]any, error) { 411 | dirname = util.NormalizePath(dirname) 412 | 413 | result, err := this.adapter.ListContents(dirname, recursive...) 414 | if err != nil { 415 | return nil, err 416 | } 417 | 418 | return result, nil 419 | } 420 | 421 | // 类型 422 | // GetMimetype 423 | func (this *Filesystem) GetMimetype(path string) (string, error) { 424 | path = util.NormalizePath(path) 425 | object, err := this.adapter.GetMimetype(path) 426 | 427 | if err != nil { 428 | return "", err 429 | } 430 | 431 | return object["mimetype"].(string), nil 432 | } 433 | 434 | // 时间戳 435 | // GetTimestamp 436 | func (this *Filesystem) GetTimestamp(path string) (int64, error) { 437 | path = util.NormalizePath(path) 438 | object, err := this.adapter.GetTimestamp(path) 439 | 440 | if err != nil { 441 | return 0, err 442 | } 443 | 444 | return object["timestamp"].(int64), nil 445 | } 446 | 447 | // 权限 448 | // GetVisibility string 449 | func (this *Filesystem) GetVisibility(path string) (string, error) { 450 | path = util.NormalizePath(path) 451 | object, err := this.adapter.GetVisibility(path) 452 | 453 | if err != nil { 454 | return "", err 455 | } 456 | 457 | return object["visibility"], nil 458 | } 459 | 460 | // 大小 461 | // GetSize 462 | func (this *Filesystem) GetSize(path string) (int64, error) { 463 | path = util.NormalizePath(path) 464 | object, err := this.adapter.GetSize(path) 465 | 466 | if err != nil { 467 | return 0, err 468 | } 469 | 470 | return object["size"].(int64), nil 471 | } 472 | 473 | // 设置权限 474 | // SetVisibility 475 | func (this *Filesystem) SetVisibility(path string, visibility string) (bool, error) { 476 | path = util.NormalizePath(path) 477 | 478 | if _, err := this.adapter.SetVisibility(path, visibility); err != nil { 479 | return false, err 480 | } 481 | 482 | return true, nil 483 | } 484 | 485 | // 信息数据 486 | // Get Metadata 487 | func (this *Filesystem) GetMetadata(path string) (map[string]any, error) { 488 | path = util.NormalizePath(path) 489 | 490 | if info, err := this.adapter.GetMetadata(path); err != nil { 491 | return nil, err 492 | } else { 493 | return info, nil 494 | } 495 | } 496 | 497 | // 获取 498 | // Get 499 | // file := Get("/file.txt").(*File) 500 | // dir := Get("/dir").(*Directory) 501 | func (this *Filesystem) Get(path string, handler ...func(*Filesystem, string) any) any { 502 | path = util.NormalizePath(path) 503 | 504 | if len(handler) > 0 { 505 | return handler[0](this, path) 506 | } 507 | 508 | data, _ := this.GetMetadata(path) 509 | 510 | if data != nil && data["type"] == "file" { 511 | file := &File{} 512 | file.SetFilesystem(this) 513 | file.SetPath(path) 514 | 515 | return file 516 | } else { 517 | dir := &Directory{} 518 | dir.SetFilesystem(this) 519 | dir.SetPath(path) 520 | 521 | return dir 522 | } 523 | } 524 | -------------------------------------------------------------------------------- /filesystem/filesystem_test.go: -------------------------------------------------------------------------------- 1 | package filesystem_test 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/deatil/go-filesystem/filesystem" 9 | local_adapter "github.com/deatil/go-filesystem/filesystem/adapter/local" 10 | ) 11 | 12 | func assertErrorT(t *testing.T) func(error, string) { 13 | return func(err error, msg string) { 14 | if err != nil { 15 | t.Errorf("Failed %s: error: %+v", msg, err) 16 | } 17 | } 18 | } 19 | 20 | func assertEqualT(t *testing.T) func(any, any, string) { 21 | return func(actual any, expected any, msg string) { 22 | if !reflect.DeepEqual(actual, expected) { 23 | t.Errorf("Failed %s: actual: %v, expected: %v", msg, actual, expected) 24 | } 25 | } 26 | } 27 | 28 | func Test_ListContents(t *testing.T) { 29 | assertEqual := assertEqualT(t) 30 | 31 | // 根目录 32 | root := "./testdata" 33 | adapter := local_adapter.New(root) 34 | 35 | // 磁盘 36 | fs := filesystem.New(adapter) 37 | 38 | // 使用 39 | res, err := fs.ListContents("/") 40 | if err != nil { 41 | t.Fatal(err.Error()) 42 | } 43 | 44 | check := map[string]any{ 45 | "path": "test.txt", 46 | "size": int64(8), 47 | "timestamp": int64(1733803713), 48 | "type": "file", 49 | } 50 | 51 | useRes := map[string]any{} 52 | for _, v := range res { 53 | if path, ok := v["path"].(string); ok && path == "test.txt" { 54 | useRes = v 55 | } 56 | } 57 | 58 | assertEqual(useRes["path"], check["path"], "Test_ListContents path") 59 | assertEqual(useRes["size"], check["size"], "Test_ListContents size") 60 | assertEqual(useRes["type"], check["type"], "Test_ListContents type") 61 | 62 | ts := check["timestamp"].(int64) 63 | if useRes["timestamp"].(int64) < ts { 64 | t.Errorf("timestamp got %d, want %d", useRes["timestamp"], ts) 65 | } 66 | } 67 | 68 | func Test_Has(t *testing.T) { 69 | assertEqual := assertEqualT(t) 70 | 71 | // 根目录 72 | root := "./testdata" 73 | adapter := local_adapter.New(root) 74 | 75 | fs := filesystem.New(adapter) 76 | 77 | res := fs.Has("/test.txt") 78 | assertEqual(res, true, "Test_Has") 79 | 80 | res2 := fs.Has("/test2.txt") 81 | assertEqual(res2, false, "Test_Has 2") 82 | } 83 | 84 | func Test_Read(t *testing.T) { 85 | assertEqual := assertEqualT(t) 86 | 87 | // 根目录 88 | root := "./testdata" 89 | adapter := local_adapter.New(root) 90 | 91 | fs := filesystem.New(adapter) 92 | 93 | res, err := fs.Read("/test.txt") 94 | if err != nil { 95 | t.Fatal(err.Error()) 96 | } 97 | 98 | assertEqual(string(res), "testdata", "Test_Read") 99 | } 100 | 101 | func Test_GetMimetype(t *testing.T) { 102 | assertEqual := assertEqualT(t) 103 | 104 | // 根目录 105 | root := "./testdata" 106 | adapter := local_adapter.New(root) 107 | 108 | fs := filesystem.New(adapter) 109 | 110 | res, err := fs.GetMimetype("/test.txt") 111 | if err != nil { 112 | t.Fatal(err.Error()) 113 | } 114 | 115 | assertEqual(res, "application/octet-stream", "Test_GetMimetype") 116 | } 117 | 118 | func Test_GetTimestamp(t *testing.T) { 119 | // 根目录 120 | root := "./testdata" 121 | adapter := local_adapter.New(root) 122 | 123 | fs := filesystem.New(adapter) 124 | 125 | res, err := fs.GetTimestamp("/test.txt") 126 | if err != nil { 127 | t.Fatal(err.Error()) 128 | } 129 | 130 | ts := int64(1733803713) 131 | if res < ts { 132 | t.Errorf("got %d, want %d", res, ts) 133 | } 134 | } 135 | 136 | func Test_GetVisibility(t *testing.T) { 137 | // 根目录 138 | root := "./testdata" 139 | adapter := local_adapter.New(root) 140 | 141 | fs := filesystem.New(adapter) 142 | 143 | res, err := fs.GetVisibility("/test.txt") 144 | if err != nil { 145 | t.Fatal(err.Error()) 146 | } 147 | 148 | if res != "666" && res != "public" { 149 | t.Error("GetVisibility fail") 150 | } 151 | } 152 | 153 | func Test_GetSize(t *testing.T) { 154 | assertEqual := assertEqualT(t) 155 | 156 | // 根目录 157 | root := "./testdata" 158 | adapter := local_adapter.New(root) 159 | 160 | fs := filesystem.New(adapter) 161 | 162 | res, err := fs.GetSize("/test.txt") 163 | if err != nil { 164 | t.Fatal(err.Error()) 165 | } 166 | 167 | assertEqual(res, int64(8), "Test_GetSize") 168 | } 169 | 170 | func Test_GetMetadata(t *testing.T) { 171 | assertEqual := assertEqualT(t) 172 | 173 | // 根目录 174 | root := "./testdata" 175 | adapter := local_adapter.New(root) 176 | 177 | // 磁盘 178 | fs := filesystem.New(adapter) 179 | 180 | // 使用 181 | res, err := fs.GetMetadata("/test.txt") 182 | if err != nil { 183 | t.Fatal(err.Error()) 184 | } 185 | 186 | check := map[string]any{ 187 | "path": "test.txt", 188 | "size": int64(8), 189 | "timestamp": int64(1733803713), 190 | "type": "file", 191 | } 192 | 193 | assertEqual(res["path"], check["path"], "Test_ListContents path") 194 | assertEqual(res["size"], check["size"], "Test_ListContents size") 195 | assertEqual(res["type"], check["type"], "Test_ListContents type") 196 | 197 | ts := check["timestamp"].(int64) 198 | if res["timestamp"].(int64) < ts { 199 | t.Errorf("timestamp got %d, want %d", res["timestamp"], ts) 200 | } 201 | } 202 | 203 | func Test_Write(t *testing.T) { 204 | assertEqual := assertEqualT(t) 205 | 206 | // 根目录 207 | root := "./testdata" 208 | adapter := local_adapter.New(root) 209 | 210 | // 磁盘 211 | fs := filesystem.New(adapter) 212 | 213 | // 使用 214 | ok, err := fs.Write("/testcopy.txt", []byte("testtestdata1111111")) 215 | if !ok { 216 | t.Fatal(err.Error()) 217 | } 218 | 219 | res2, err := fs.Read("/testcopy.txt") 220 | if err != nil { 221 | t.Fatal(err.Error()) 222 | } 223 | 224 | assertEqual(string(res2), "testtestdata1111111", "Test_Write") 225 | 226 | // 使用 227 | ok, err = fs.Write("/testcopy.txt", []byte("testdata")) 228 | if !ok { 229 | t.Fatal(err.Error()) 230 | } 231 | } 232 | 233 | func Test_Put(t *testing.T) { 234 | assertEqual := assertEqualT(t) 235 | 236 | // 根目录 237 | root := "./testdata" 238 | adapter := local_adapter.New(root) 239 | 240 | // 磁盘 241 | fs := filesystem.New(adapter) 242 | 243 | // 使用 244 | ok, err := fs.Put("/testcopy.txt", []byte("222222222")) 245 | if !ok { 246 | t.Fatal(err.Error()) 247 | } 248 | 249 | res2, err := fs.Read("/testcopy.txt") 250 | if err != nil { 251 | t.Fatal(err.Error()) 252 | } 253 | 254 | assertEqual(string(res2), "222222222", "Test_Put") 255 | 256 | // 使用 257 | ok, err = fs.Write("/testcopy.txt", []byte("testdata")) 258 | if !ok { 259 | t.Fatal(err.Error()) 260 | } 261 | } 262 | 263 | func Test_Prepend(t *testing.T) { 264 | assertEqual := assertEqualT(t) 265 | 266 | // 根目录 267 | root := "./testdata" 268 | adapter := local_adapter.New(root) 269 | 270 | // 磁盘 271 | fs := filesystem.New(adapter) 272 | 273 | // 使用 274 | ok, err := fs.Prepend("/testcopy.txt", []byte("222222222")) 275 | if !ok { 276 | t.Fatal(err.Error()) 277 | } 278 | 279 | res2, err := fs.Read("/testcopy.txt") 280 | if err != nil { 281 | t.Fatal(err.Error()) 282 | } 283 | 284 | assertEqual(string(res2), "222222222testdata", "Test_Prepend") 285 | 286 | // 使用 287 | ok, err = fs.Write("/testcopy.txt", []byte("testdata")) 288 | if !ok { 289 | t.Fatal(err.Error()) 290 | } 291 | } 292 | 293 | func Test_PrependStream(t *testing.T) { 294 | assertEqual := assertEqualT(t) 295 | 296 | // 根目录 297 | root := "./testdata" 298 | adapter := local_adapter.New(root) 299 | 300 | // 磁盘 301 | fs := filesystem.New(adapter) 302 | 303 | prependDdata := bytes.NewBuffer([]byte("222222222")) 304 | 305 | // 使用 306 | ok, err := fs.PrependStream("/testcopy.txt", prependDdata) 307 | if !ok { 308 | t.Fatal(err.Error()) 309 | } 310 | 311 | res2, err := fs.Read("/testcopy.txt") 312 | if err != nil { 313 | t.Fatal(err.Error()) 314 | } 315 | 316 | assertEqual(string(res2), "222222222testdata", "Test_PrependStream") 317 | 318 | // 使用 319 | ok, err = fs.Write("/testcopy.txt", []byte("testdata")) 320 | if !ok { 321 | t.Fatal(err.Error()) 322 | } 323 | } 324 | 325 | func Test_Append(t *testing.T) { 326 | assertEqual := assertEqualT(t) 327 | 328 | // 根目录 329 | root := "./testdata" 330 | adapter := local_adapter.New(root) 331 | 332 | // 磁盘 333 | fs := filesystem.New(adapter) 334 | 335 | // 使用 336 | ok, err := fs.Append("/testcopy.txt", []byte("222222222")) 337 | if !ok { 338 | t.Fatal(err.Error()) 339 | } 340 | 341 | res2, err := fs.Read("/testcopy.txt") 342 | if err != nil { 343 | t.Fatal(err.Error()) 344 | } 345 | 346 | assertEqual(string(res2), "testdata222222222", "Test_Append") 347 | 348 | // 使用 349 | ok, err = fs.Write("/testcopy.txt", []byte("testdata")) 350 | if !ok { 351 | t.Fatal(err.Error()) 352 | } 353 | } 354 | 355 | func Test_AppendStream(t *testing.T) { 356 | assertEqual := assertEqualT(t) 357 | 358 | // 根目录 359 | root := "./testdata" 360 | adapter := local_adapter.New(root) 361 | 362 | // 磁盘 363 | fs := filesystem.New(adapter) 364 | 365 | appendData := bytes.NewBuffer([]byte("222222222")) 366 | 367 | // 使用 368 | ok, err := fs.AppendStream("/testcopy.txt", appendData) 369 | if !ok { 370 | t.Fatal(err.Error()) 371 | } 372 | 373 | res2, err := fs.Read("/testcopy.txt") 374 | if err != nil { 375 | t.Fatal(err.Error()) 376 | } 377 | 378 | assertEqual(string(res2), "testdata222222222", "Test_AppendStream") 379 | 380 | // 使用 381 | ok, err = fs.Write("/testcopy.txt", []byte("testdata")) 382 | if !ok { 383 | t.Fatal(err.Error()) 384 | } 385 | } 386 | 387 | func Test_Rename(t *testing.T) { 388 | assertEqual := assertEqualT(t) 389 | 390 | // 根目录 391 | root := "./testdata" 392 | adapter := local_adapter.New(root) 393 | 394 | // 磁盘 395 | fs := filesystem.New(adapter) 396 | 397 | // 使用 398 | ok, err := fs.Rename("/testcopy.txt", "/testcopy222.txt") 399 | if !ok { 400 | t.Fatal(err.Error()) 401 | } 402 | 403 | res2 := fs.Has("/testcopy222.txt") 404 | assertEqual(res2, true, "Test_Rename") 405 | 406 | // 使用 407 | ok, err = fs.Rename("/testcopy222.txt", "/testcopy.txt") 408 | if !ok { 409 | t.Fatal(err.Error()) 410 | } 411 | 412 | res3 := fs.Has("/testcopy222.txt") 413 | assertEqual(res3, false, "Test_Rename Rename 1") 414 | 415 | res33 := fs.Has("/testcopy.txt") 416 | assertEqual(res33, true, "Test_Rename Rename 2") 417 | 418 | } 419 | 420 | func Test_Copy(t *testing.T) { 421 | assertEqual := assertEqualT(t) 422 | 423 | // 根目录 424 | root := "./testdata" 425 | adapter := local_adapter.New(root) 426 | 427 | fs := filesystem.New(adapter) 428 | 429 | res, err := fs.Copy("/testcopy.txt", "/newtestcopy.txt") 430 | if err != nil { 431 | t.Fatal(err.Error()) 432 | } 433 | 434 | assertEqual(res, true, "Test_Copy") 435 | 436 | res2 := fs.Has("/newtestcopy.txt") 437 | assertEqual(res2, true, "Test_Copy Has") 438 | 439 | res3, _ := fs.Delete("/newtestcopy.txt") 440 | assertEqual(res3, true, "Test_Copy Delete") 441 | 442 | res33 := fs.Has("/newtestcopy.txt") 443 | assertEqual(res33, false, "Test_Copy Delete after Has") 444 | } 445 | 446 | func Test_CreateDir(t *testing.T) { 447 | assertEqual := assertEqualT(t) 448 | 449 | // 根目录 450 | root := "./testdata" 451 | adapter := local_adapter.New(root) 452 | 453 | fs := filesystem.New(adapter) 454 | 455 | res, err := fs.CreateDir("/testdir") 456 | if err != nil { 457 | t.Fatal(err.Error()) 458 | } 459 | 460 | assertEqual(res, true, "Test_CreateDir") 461 | 462 | res2 := fs.Has("/testdir") 463 | assertEqual(res2, true, "Test_CreateDir Has") 464 | 465 | res3, _ := fs.DeleteDir("/testdir") 466 | assertEqual(res3, true, "Test_CreateDir Delete") 467 | 468 | res33 := fs.Has("/testdir") 469 | assertEqual(res33, false, "Test_CreateDir Delete after Has") 470 | } 471 | 472 | func Test_HasDir(t *testing.T) { 473 | assertEqual := assertEqualT(t) 474 | 475 | // 根目录 476 | root := "./testdata" 477 | adapter := local_adapter.New(root) 478 | 479 | fs := filesystem.New(adapter) 480 | 481 | res := fs.Has("/testdir222") 482 | assertEqual(res, true, "Test_HasDir") 483 | 484 | res2 := fs.Has("/testdir333") 485 | assertEqual(res2, false, "Test_HasDir 2") 486 | } 487 | 488 | func Test_ReadAndDelete(t *testing.T) { 489 | assertEqual := assertEqualT(t) 490 | 491 | // 根目录 492 | root := "./testdata" 493 | adapter := local_adapter.New(root) 494 | 495 | fs := filesystem.New(adapter) 496 | 497 | res, err := fs.Copy("/testcopy.txt", "/testReadAndDelete.txt") 498 | if err != nil { 499 | t.Fatal(err.Error()) 500 | } 501 | 502 | assertEqual(res, true, "Test_ReadAndDelete") 503 | 504 | res2 := fs.Has("/testReadAndDelete.txt") 505 | assertEqual(res2, true, "Test_ReadAndDelete Has") 506 | 507 | res3, err := fs.ReadAndDelete("/testReadAndDelete.txt") 508 | if err != nil { 509 | t.Fatal(err.Error()) 510 | } 511 | 512 | assertEqual(string(res3), "testdata", "Test_ReadAndDelete ReadAndDelete") 513 | 514 | res33 := fs.Has("/testReadAndDelete.txt") 515 | assertEqual(res33, false, "Test_ReadAndDelete ReadAndDelete after Has") 516 | } 517 | -------------------------------------------------------------------------------- /filesystem/handler.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | /** 4 | * 扩展基础类 5 | * 6 | * @create 2021-8-1 7 | * @author deatil 8 | */ 9 | type Handler struct { 10 | filesystem *Filesystem 11 | path string 12 | } 13 | 14 | // 是否为文件夹 15 | func (this *Handler) IsDir() bool { 16 | return this.GetType() == "dir" 17 | } 18 | 19 | // 是否为文件 20 | func (this *Handler) IsFile() bool { 21 | return this.GetType() == "file" 22 | } 23 | 24 | // 类型 25 | func (this *Handler) GetType() string { 26 | metadata, _ := this.filesystem.GetMetadata(this.path) 27 | if metadata == nil { 28 | return "dir" 29 | } 30 | 31 | return metadata["type"].(string) 32 | } 33 | 34 | // 设置文件系统 35 | func (this *Handler) SetFilesystem(filesystem *Filesystem) any { 36 | this.filesystem = filesystem 37 | 38 | return this 39 | } 40 | 41 | // 获取文件系统 42 | func (this *Handler) GetFilesystem() *Filesystem { 43 | return this.filesystem 44 | } 45 | 46 | // 设置目录 47 | func (this *Handler) SetPath(path string) any { 48 | this.path = path 49 | 50 | return this 51 | } 52 | 53 | // 获取目录 54 | func (this *Handler) GetPath() string { 55 | return this.path 56 | } 57 | -------------------------------------------------------------------------------- /filesystem/interfaces/adapter.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import( 4 | "io" 5 | ) 6 | 7 | /** 8 | * 适配器接口 9 | * 10 | * @create 2021-8-1 11 | * @author deatil 12 | */ 13 | type Adapter interface { 14 | // 设置前缀 15 | SetPathPrefix(string) 16 | 17 | // 获取前缀 18 | GetPathPrefix() string 19 | 20 | // 添加前缀 21 | ApplyPathPrefix(string) string 22 | 23 | // 移除前缀 24 | RemovePathPrefix(string) string 25 | 26 | // 判断 27 | Has(string) bool 28 | 29 | // 上传 30 | Write(string, []byte, Config) (map[string]any, error) 31 | 32 | // 上传 Stream 文件类型 33 | WriteStream(string, io.Reader, Config) (map[string]any, error) 34 | 35 | // 更新 36 | Update(string, []byte, Config) (map[string]any, error) 37 | 38 | // 更新 39 | UpdateStream(string, io.Reader, Config) (map[string]any, error) 40 | 41 | // 读取 42 | Read(string) (map[string]any, error) 43 | 44 | // 读取文件为数据流 45 | ReadStream(string) (map[string]any, error) 46 | 47 | // 重命名 48 | Rename(string, string) error 49 | 50 | // 复制 51 | Copy(string, string) error 52 | 53 | // 删除 54 | Delete(string) error 55 | 56 | // 删除文件夹 57 | DeleteDir(string) error 58 | 59 | // 创建文件夹 60 | CreateDir(string, Config) (map[string]string, error) 61 | 62 | // 列出内容 63 | ListContents(string, ...bool) ([]map[string]any, error) 64 | 65 | // 文件信息 66 | GetMetadata(string) (map[string]any, error) 67 | 68 | // 文件大小 69 | GetSize(string) (map[string]any, error) 70 | 71 | // 类型 72 | GetMimetype(string) (map[string]any, error) 73 | 74 | // 获取时间戳 75 | GetTimestamp(string) (map[string]any, error) 76 | 77 | // 获取文件的权限 78 | GetVisibility(string) (map[string]string, error) 79 | 80 | // 设置文件的权限 81 | SetVisibility(string, string) (map[string]string, error) 82 | } 83 | -------------------------------------------------------------------------------- /filesystem/interfaces/config.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | /** 4 | * 配置接口 5 | * 6 | * @create 2021-8-1 7 | * @author deatil 8 | */ 9 | type Config interface { 10 | // 覆盖旧数据 11 | With(map[string]any) Config 12 | 13 | // 设置单个新数据 14 | Set(string, any) Config 15 | 16 | // 是否存在 17 | Has(string) bool 18 | 19 | // 获取一个带默认的值 20 | Get(string, ...any) any 21 | } 22 | -------------------------------------------------------------------------------- /filesystem/mount_manager.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | import( 4 | "io" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | /** 10 | * 文件系统 11 | * 12 | * @create 2021-8-7 13 | * @author deatil 14 | */ 15 | type MountManager struct { 16 | filesystems map[string]*Filesystem 17 | } 18 | 19 | // 文件系统实例化 20 | func NewMountManager(filesystems ...map[string]any) *MountManager { 21 | mm := &MountManager{ 22 | filesystems: make(map[string]*Filesystem), 23 | } 24 | 25 | if len(filesystems) > 0{ 26 | mm.MountFilesystems(filesystems[0]) 27 | } 28 | 29 | return mm 30 | } 31 | 32 | // 批量 33 | func (this *MountManager) MountFilesystems(filesystems map[string]any) *MountManager { 34 | for prefix, filesystem := range filesystems { 35 | this.MountFilesystem(prefix, filesystem.(*Filesystem)) 36 | } 37 | 38 | return this 39 | } 40 | 41 | // 单独 42 | func (this *MountManager) MountFilesystem(prefix string, filesystem *Filesystem) *MountManager { 43 | this.filesystems[prefix] = filesystem 44 | 45 | return this 46 | } 47 | 48 | // 获取文件管理器 49 | func (this *MountManager) GetFilesystem(prefix string) *Filesystem { 50 | if _, ok := this.filesystems[prefix]; !ok { 51 | panic("go-filesystem: [" + prefix + "] prefix not exists") 52 | } 53 | 54 | return this.filesystems[prefix] 55 | } 56 | 57 | // 过滤 58 | // [:prefix, :arguments] 59 | func (this *MountManager) FilterPrefix(arguments []string) (string, []string) { 60 | if len(arguments) < 1 { 61 | panic("go-filesystem: arguments slice not empty") 62 | } 63 | 64 | path := arguments[0] 65 | 66 | prefix, path := this.GetPrefixAndPath(path) 67 | 68 | newArguments := make([]string, len(arguments)) 69 | newArguments = append(newArguments, path) 70 | newArguments = append(newArguments, arguments[1:]...) 71 | 72 | return prefix, newArguments 73 | } 74 | 75 | // 获取前缀和路径 76 | // [:prefix, :path] 77 | func (this *MountManager) GetPrefixAndPath(path string) (string, string) { 78 | paths := strings.SplitN(path, "://", 2) 79 | 80 | if len(paths) < 1 { 81 | panic("go-filesystem: " + path + "'prefix not exists") 82 | } 83 | 84 | return paths[0], paths[1] 85 | } 86 | 87 | // 列出内容 88 | func (this *MountManager) ListContents(directory string, recursive ...bool) ([]map[string]any, error) { 89 | prefix, dir := this.GetPrefixAndPath(directory) 90 | 91 | filesystem := this.GetFilesystem(prefix) 92 | 93 | result, err := filesystem.ListContents(dir, recursive...) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | for key, item := range result { 99 | item["filesystem"] = prefix 100 | result[key] = item 101 | } 102 | 103 | return result, nil 104 | } 105 | 106 | // 复制 107 | func (this *MountManager) Copy(from string, to string, conf ...map[string]any) (bool, error) { 108 | prefixFrom, pathFrom := this.GetPrefixAndPath(from) 109 | 110 | buffer, err := this.GetFilesystem(prefixFrom).ReadStream(pathFrom) 111 | if err != nil { 112 | return false, err 113 | } 114 | 115 | // 手动关闭文件流 116 | defer buffer.Close() 117 | 118 | prefixTo, pathTo := this.GetPrefixAndPath(to) 119 | 120 | result, err2 := this.GetFilesystem(prefixTo).WriteStream(pathTo, buffer, conf...) 121 | if err2 != nil { 122 | return false, err2 123 | } 124 | 125 | return result, nil 126 | } 127 | 128 | // 移动 129 | func (this *MountManager) Move(from string, to string, conf ...map[string]any) (bool, error) { 130 | prefixFrom, pathFrom := this.GetPrefixAndPath(from) 131 | prefixTo, pathTo := this.GetPrefixAndPath(to) 132 | 133 | if prefixFrom == prefixTo { 134 | filesystem := this.GetFilesystem(prefixFrom) 135 | 136 | renamed, err := filesystem.Rename(pathFrom, pathTo) 137 | if err != nil { 138 | return false, err 139 | } 140 | 141 | if len(conf) > 0 { 142 | if visibility, ok := conf[0]["visibility"]; ok && renamed { 143 | return filesystem.SetVisibility(pathTo, visibility.(string)) 144 | } 145 | } 146 | 147 | return renamed, nil 148 | } 149 | 150 | copied, err := this.Copy(from, to, conf...) 151 | if copied { 152 | return this.Delete(from) 153 | } 154 | 155 | return false, err 156 | } 157 | 158 | // 判断 159 | func (this *MountManager) Has(path string) bool { 160 | prefix, newPath := this.GetPrefixAndPath(path) 161 | 162 | return this.GetFilesystem(prefix).Has(newPath) 163 | } 164 | 165 | // 文件到字符 166 | func (this *MountManager) Read(path string) ([]byte, error) { 167 | prefix, newPath := this.GetPrefixAndPath(path) 168 | 169 | return this.GetFilesystem(prefix).Read(newPath) 170 | } 171 | 172 | // 读取成数据流 173 | func (this *MountManager) ReadStream(path string) (*os.File, error) { 174 | prefix, newPath := this.GetPrefixAndPath(path) 175 | 176 | return this.GetFilesystem(prefix).ReadStream(newPath) 177 | } 178 | 179 | // 信息数据 180 | func (this *MountManager) GetMetadata(path string) (map[string]any, error) { 181 | prefix, newPath := this.GetPrefixAndPath(path) 182 | 183 | return this.GetFilesystem(prefix).GetMetadata(newPath) 184 | } 185 | 186 | // 大小 187 | func (this *MountManager) GetSize(path string) (int64, error) { 188 | prefix, newPath := this.GetPrefixAndPath(path) 189 | 190 | return this.GetFilesystem(prefix).GetSize(newPath) 191 | } 192 | 193 | // 类型 194 | func (this *MountManager) GetMimetype(path string) (string, error) { 195 | prefix, newPath := this.GetPrefixAndPath(path) 196 | 197 | return this.GetFilesystem(prefix).GetMimetype(newPath) 198 | } 199 | 200 | // 时间戳 201 | func (this *MountManager) GetTimestamp(path string) (int64, error) { 202 | prefix, newPath := this.GetPrefixAndPath(path) 203 | 204 | return this.GetFilesystem(prefix).GetTimestamp(newPath) 205 | } 206 | 207 | // 权限 208 | func (this *MountManager) GetVisibility(path string) (string, error) { 209 | prefix, newPath := this.GetPrefixAndPath(path) 210 | 211 | return this.GetFilesystem(prefix).GetVisibility(newPath) 212 | } 213 | 214 | // 写入文件 215 | func (this *MountManager) Write(path string, contents []byte, conf ...map[string]any) (bool, error) { 216 | prefix, newPath := this.GetPrefixAndPath(path) 217 | 218 | return this.GetFilesystem(prefix).Write(newPath, contents, conf...) 219 | } 220 | 221 | // 写入数据流 222 | func (this *MountManager) WriteStream(path string, resource io.Reader, conf ...map[string]any) (bool, error) { 223 | prefix, newPath := this.GetPrefixAndPath(path) 224 | 225 | return this.GetFilesystem(prefix).WriteStream(newPath, resource, conf...) 226 | } 227 | 228 | // 更新字符 229 | func (this *MountManager) Update(path string, contents []byte, conf ...map[string]any) (bool, error) { 230 | prefix, newPath := this.GetPrefixAndPath(path) 231 | 232 | return this.GetFilesystem(prefix).Update(newPath, contents, conf...) 233 | } 234 | 235 | // 更新数据流 236 | func (this *MountManager) UpdateStream(path string, resource io.Reader, conf ...map[string]any) (bool, error) { 237 | prefix, newPath := this.GetPrefixAndPath(path) 238 | 239 | return this.GetFilesystem(prefix).UpdateStream(newPath, resource, conf...) 240 | } 241 | 242 | // 重命名 243 | func (this *MountManager) Rename(path string, newpath string) (bool, error) { 244 | prefix, pather := this.GetPrefixAndPath(path) 245 | 246 | return this.GetFilesystem(prefix).Rename(pather, newpath) 247 | } 248 | 249 | // 删除 250 | func (this *MountManager) Delete(path string) (bool, error) { 251 | prefix, newPath := this.GetPrefixAndPath(path) 252 | 253 | return this.GetFilesystem(prefix).Delete(newPath) 254 | } 255 | 256 | // 删除文件夹 257 | func (this *MountManager) DeleteDir(dirname string) (bool, error) { 258 | prefix, newDirname := this.GetPrefixAndPath(dirname) 259 | 260 | return this.GetFilesystem(prefix).DeleteDir(newDirname) 261 | } 262 | 263 | // 创建文件夹 264 | func (this *MountManager) CreateDir(dirname string, conf ...map[string]any) (bool, error) { 265 | prefix, newDirname := this.GetPrefixAndPath(dirname) 266 | 267 | return this.GetFilesystem(prefix).CreateDir(newDirname, conf...) 268 | } 269 | 270 | // 设置权限 271 | func (this *MountManager) SetVisibility(path string, visibility string) (bool, error) { 272 | prefix, newPath := this.GetPrefixAndPath(path) 273 | 274 | return this.GetFilesystem(prefix).SetVisibility(newPath, visibility) 275 | } 276 | 277 | // 更新 278 | func (this *MountManager) Put(path string, contents []byte, conf ...map[string]any) (bool, error) { 279 | prefix, newPath := this.GetPrefixAndPath(path) 280 | 281 | return this.GetFilesystem(prefix).Put(newPath, contents, conf...) 282 | } 283 | 284 | // 更新数据流 285 | func (this *MountManager) PutStream(path string, resource io.Reader, conf ...map[string]any) (bool, error) { 286 | prefix, newPath := this.GetPrefixAndPath(path) 287 | 288 | return this.GetFilesystem(prefix).PutStream(newPath, resource, conf...) 289 | } 290 | 291 | // 读取并删除 292 | func (this *MountManager) ReadAndDelete(path string) (any, error) { 293 | prefix, newPath := this.GetPrefixAndPath(path) 294 | 295 | return this.GetFilesystem(prefix).ReadAndDelete(newPath) 296 | } 297 | 298 | // 获取 299 | // file := Get("/file.txt").(*File) 300 | // dir := Get("/dir").(*Directory) 301 | func (this *MountManager) Get(path string, handler ...func(*Filesystem, string) any) any { 302 | prefix, newPath := this.GetPrefixAndPath(path) 303 | 304 | return this.GetFilesystem(prefix).Get(newPath, handler...) 305 | } 306 | -------------------------------------------------------------------------------- /filesystem/testdata/test.txt: -------------------------------------------------------------------------------- 1 | testdata -------------------------------------------------------------------------------- /filesystem/testdata/testcopy.txt: -------------------------------------------------------------------------------- 1 | testdata -------------------------------------------------------------------------------- /filesystem/testdata/testdir222/test.txt: -------------------------------------------------------------------------------- 1 | testdata -------------------------------------------------------------------------------- /filesystem/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import( 4 | "path" 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | /** 10 | * 文件管理器 11 | * 12 | * @create 2021-8-1 13 | * @author deatil 14 | */ 15 | 16 | func NormalizeDirname(dirname string) string { 17 | if dirname == "." { 18 | return "" 19 | } 20 | 21 | return dirname 22 | } 23 | 24 | func Dirname(name string) string { 25 | return NormalizeDirname(path.Dir(name)) 26 | } 27 | 28 | func NormalizePath(path string) string { 29 | return NormalizeRelativePath(path) 30 | } 31 | 32 | func NormalizeRelativePath(path string) string { 33 | path = strings.Replace(path, "\\", "/", -1) 34 | path = RemoveFunkyWhiteSpace(path) 35 | 36 | var parts []string 37 | 38 | paths := strings.Split(path, "/") 39 | for _, part := range paths { 40 | if part == ".." && len(parts) > 0 { 41 | parts = parts[1:] 42 | } else if part != "" && part != "." { 43 | parts = append(parts, part) 44 | } 45 | } 46 | 47 | return strings.Join(parts, "/") 48 | } 49 | 50 | func RemoveFunkyWhiteSpace(path string) string { 51 | re, _ := regexp.Compile(`\p{C}+|^\./`) 52 | 53 | for { 54 | if !re.MatchString(path) { 55 | break 56 | } 57 | 58 | path = re.ReplaceAllString(path, "") 59 | } 60 | 61 | return path 62 | } 63 | 64 | func NormalizePrefix(prefix string, separator string) string { 65 | return strings.TrimSuffix(prefix, separator) + separator 66 | } 67 | 68 | func Basename(fp string) string { 69 | return path.Base(fp) 70 | } 71 | 72 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/deatil/go-filesystem 2 | 3 | go 1.20 4 | 5 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deatil/go-filesystem/da47e5c5b8275848a0ff2b3417756aa5abc0dad4/logo.png --------------------------------------------------------------------------------