github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/storage/local.go (about) 1 // Copyright 2023 The GitBundle Inc. All rights reserved. 2 // Copyright 2017 The Gitea Authors. All rights reserved. 3 // Use of this source code is governed by a MIT-style 4 // license that can be found in the LICENSE file. 5 6 package storage 7 8 import ( 9 "context" 10 "io" 11 "net/url" 12 "os" 13 "path" 14 "path/filepath" 15 "strings" 16 17 "github.com/gitbundle/modules/log" 18 "github.com/gitbundle/modules/util" 19 ) 20 21 var _ ObjectStorage = &LocalStorage{} 22 23 // LocalStorageType is the type descriptor for local storage 24 const LocalStorageType Type = "local" 25 26 // LocalStorageConfig represents the configuration for a local storage 27 type LocalStorageConfig struct { 28 Path string `ini:"PATH"` 29 TemporaryPath string `ini:"TEMPORARY_PATH"` 30 } 31 32 // LocalStorage represents a local files storage 33 type LocalStorage struct { 34 ctx context.Context 35 dir string 36 tmpdir string 37 } 38 39 // NewLocalStorage returns a local files 40 func NewLocalStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error) { 41 configInterface, err := toConfig(LocalStorageConfig{}, cfg) 42 if err != nil { 43 return nil, err 44 } 45 config := configInterface.(LocalStorageConfig) 46 47 log.Info("Creating new Local Storage at %s", config.Path) 48 if err := os.MkdirAll(config.Path, os.ModePerm); err != nil { 49 return nil, err 50 } 51 52 if config.TemporaryPath == "" { 53 config.TemporaryPath = config.Path + "/tmp" 54 } 55 56 return &LocalStorage{ 57 ctx: ctx, 58 dir: config.Path, 59 tmpdir: config.TemporaryPath, 60 }, nil 61 } 62 63 func (l *LocalStorage) buildLocalPath(p string) string { 64 return filepath.Join(l.dir, path.Clean("/" + strings.ReplaceAll(p, "\\", "/"))[1:]) 65 } 66 67 // Open a file 68 func (l *LocalStorage) Open(path string) (Object, error) { 69 return os.Open(l.buildLocalPath(path)) 70 } 71 72 // Save a file 73 func (l *LocalStorage) Save(path string, r io.Reader, size int64) (int64, error) { 74 p := l.buildLocalPath(path) 75 if err := os.MkdirAll(filepath.Dir(p), os.ModePerm); err != nil { 76 return 0, err 77 } 78 79 // Create a temporary file to save to 80 if err := os.MkdirAll(l.tmpdir, os.ModePerm); err != nil { 81 return 0, err 82 } 83 tmp, err := os.CreateTemp(l.tmpdir, "upload-*") 84 if err != nil { 85 return 0, err 86 } 87 tmpRemoved := false 88 defer func() { 89 if !tmpRemoved { 90 _ = util.Remove(tmp.Name()) 91 } 92 }() 93 94 n, err := io.Copy(tmp, r) 95 if err != nil { 96 return 0, err 97 } 98 99 if err := tmp.Close(); err != nil { 100 return 0, err 101 } 102 103 if err := util.Rename(tmp.Name(), p); err != nil { 104 return 0, err 105 } 106 107 tmpRemoved = true 108 109 return n, nil 110 } 111 112 // Stat returns the info of the file 113 func (l *LocalStorage) Stat(path string) (os.FileInfo, error) { 114 return os.Stat(l.buildLocalPath(path)) 115 } 116 117 // Delete delete a file 118 func (l *LocalStorage) Delete(path string) error { 119 return util.Remove(l.buildLocalPath(path)) 120 } 121 122 // URL gets the redirect URL to a file 123 func (l *LocalStorage) URL(path, name string) (*url.URL, error) { 124 return nil, ErrURLNotSupported 125 } 126 127 // IterateObjects iterates across the objects in the local storage 128 func (l *LocalStorage) IterateObjects(fn func(path string, obj Object) error) error { 129 return filepath.Walk(l.dir, func(path string, info os.FileInfo, err error) error { 130 if err != nil { 131 return err 132 } 133 select { 134 case <-l.ctx.Done(): 135 return l.ctx.Err() 136 default: 137 } 138 if path == l.dir { 139 return nil 140 } 141 if info.IsDir() { 142 return nil 143 } 144 relPath, err := filepath.Rel(l.dir, path) 145 if err != nil { 146 return err 147 } 148 obj, err := os.Open(path) 149 if err != nil { 150 return err 151 } 152 defer obj.Close() 153 return fn(relPath, obj) 154 }) 155 } 156 157 func init() { 158 RegisterStorageType(LocalStorageType, NewLocalStorage) 159 }