github.com/viant/toolbox@v0.34.5/storage/copy.go (about) 1 package storage 2 3 import ( 4 "archive/tar" 5 "archive/zip" 6 "bytes" 7 "fmt" 8 "github.com/viant/toolbox" 9 "io" 10 "io/ioutil" 11 "path" 12 "strings" 13 ) 14 15 type CopyHandler func(sourceObject Object, source io.Reader, destinationService Service, destinationURL string) error 16 type ModificationHandler func(reader io.ReadCloser) (io.ReadCloser, error) 17 18 func urlPath(URL string) string { 19 var result = URL 20 schemaPosition := strings.Index(URL, "://") 21 if schemaPosition != -1 { 22 result = string(URL[schemaPosition+3:]) 23 } 24 pathRoot := strings.Index(result, "/") 25 if pathRoot > 0 { 26 result = string(result[pathRoot:]) 27 } 28 if strings.HasSuffix(result, "/") { 29 result = string(result[:len(result)-1]) 30 } 31 32 return result 33 } 34 35 func truncatePath(path string) string { 36 if len(path) <= 1 { 37 return path 38 } 39 if strings.HasSuffix(path, "/") { 40 return string(path[:len(path)-1]) 41 } 42 return path 43 } 44 45 func copyStorageContent(sourceService Service, sourceURL string, destinationService Service, destinationURL string, modifyContentHandler ModificationHandler, subPath string, copyHandler CopyHandler) error { 46 sourceListURL := sourceURL 47 if subPath != "" { 48 sourceListURL = toolbox.URLPathJoin(sourceURL, subPath) 49 } 50 objects, err := sourceService.List(sourceListURL) 51 if err != nil { 52 return err 53 } 54 55 for _, object := range objects { 56 if err = copyObject(object, sourceService, sourceURL, destinationService, destinationURL, modifyContentHandler, subPath, copyHandler); err != nil { 57 return err 58 } 59 } 60 return nil 61 } 62 63 func copyObject(object Object, sourceService Service, sourceURL string, destinationService Service, destinationURL string, modifyContentHandler ModificationHandler, subPath string, copyHandler CopyHandler) error { 64 var objectRelativePath string 65 sourceURLPath := urlPath(sourceURL) 66 67 var objectURLPath = urlPath(object.URL()) 68 if object.IsFolder() { 69 if truncatePath(sourceURLPath) == truncatePath(objectURLPath) { 70 return nil 71 } 72 if subPath != "" && objectURLPath == toolbox.URLPathJoin(sourceURLPath, subPath) { 73 return nil 74 } 75 } 76 if len(objectURLPath) > len(sourceURLPath) { 77 objectRelativePath = objectURLPath[len(sourceURLPath):] 78 if strings.HasPrefix(objectRelativePath, "/") { 79 objectRelativePath = string(objectRelativePath[1:]) 80 } 81 } 82 var destinationObjectURL = destinationURL 83 if objectRelativePath != "" { 84 destinationObjectURL = toolbox.URLPathJoin(destinationURL, objectRelativePath) 85 } 86 87 if object.IsContent() { 88 reader, err := sourceService.Download(object) 89 if err != nil { 90 err = fmt.Errorf("unable download, %v -> %v, %v", object.URL(), destinationObjectURL, err) 91 return err 92 } 93 defer reader.Close() 94 95 if modifyContentHandler != nil { 96 content, err := ioutil.ReadAll(reader) 97 if err != nil { 98 return err 99 } 100 reader = ioutil.NopCloser(bytes.NewReader(content)) 101 reader, err = modifyContentHandler(reader) 102 if err != nil { 103 err = fmt.Errorf("unable modify content, %v %v %v", object.URL(), destinationObjectURL, err) 104 return err 105 } 106 } 107 108 if subPath == "" { 109 _, sourceName := path.Split(object.URL()) 110 _, destinationName := path.Split(destinationURL) 111 if strings.HasSuffix(destinationObjectURL, "/") { 112 destinationObjectURL = toolbox.URLPathJoin(destinationObjectURL, sourceName) 113 } else { 114 destinationObject, _ := destinationService.StorageObject(destinationObjectURL) 115 if destinationObject != nil && destinationObject.IsFolder() { 116 destinationObjectURL = toolbox.URLPathJoin(destinationObjectURL, sourceName) 117 } else if destinationName != sourceName { 118 if !strings.Contains(destinationName, ".") { 119 destinationObjectURL = toolbox.URLPathJoin(destinationURL, sourceName) 120 } 121 122 } 123 } 124 } 125 err = copyHandler(object, reader, destinationService, destinationObjectURL) 126 if err != nil { 127 return err 128 } 129 130 } else { 131 if err := copyStorageContent(sourceService, sourceURL, destinationService, destinationURL, modifyContentHandler, objectRelativePath, copyHandler); err != nil { 132 return err 133 } 134 } 135 return nil 136 } 137 138 func copySourceToDestination(sourceObject Object, reader io.Reader, destinationService Service, destinationURL string) error { 139 mode := DefaultFileMode 140 if fileInfo := sourceObject.FileInfo(); fileInfo != nil { 141 mode = fileInfo.Mode() 142 } 143 err := destinationService.UploadWithMode(destinationURL, mode, reader) 144 if err != nil { 145 err = fmt.Errorf("unable upload, %v %v %v", sourceObject.URL(), destinationURL, err) 146 } 147 return err 148 } 149 150 func getArchiveCopyHandler(archive *zip.Writer, parentURL string) CopyHandler { 151 152 return func(sourceObject Object, reader io.Reader, destinationService Service, destinationURL string) error { 153 var _, relativePath = toolbox.URLSplit(destinationURL) 154 if destinationURL != parentURL { 155 relativePath = strings.Replace(destinationURL, parentURL, "", 1) 156 } 157 158 header, err := zip.FileInfoHeader(sourceObject.FileInfo()) 159 if err != nil { 160 return err 161 } 162 header.Method = zip.Deflate 163 header.Name = relativePath 164 writer, err := archive.CreateHeader(header) 165 if err != nil { 166 return err 167 } 168 _, err = io.Copy(writer, reader) 169 return err 170 } 171 } 172 173 //Copy downloads objects from source URL to upload them to destination URL. 174 func Copy(sourceService Service, sourceURL string, destinationService Service, destinationURL string, modifyContentHandler ModificationHandler, copyHandler CopyHandler) (err error) { 175 if copyHandler == nil { 176 copyHandler = copySourceToDestination 177 } 178 if strings.HasSuffix(sourceURL, "//") { 179 sourceURL = string(sourceURL[:len(sourceURL)-1]) 180 } 181 err = copyStorageContent(sourceService, sourceURL, destinationService, destinationURL, modifyContentHandler, "", copyHandler) 182 if err != nil { 183 err = fmt.Errorf("failed to copy %v -> %v: %v", sourceURL, destinationURL, err) 184 } 185 return err 186 } 187 188 //Archive archives supplied URL assets into zip writer 189 func Archive(service Service, URL string, writer *zip.Writer) error { 190 memService := NewMemoryService() 191 var destURL = "mem:///dev/nul" 192 return Copy(service, URL, memService, destURL, nil, getArchiveCopyHandler(writer, destURL)) 193 } 194 195 func getArchiveCopyHandlerWithFilter(archive *zip.Writer, parentURL string, predicate func(candidate Object) bool) CopyHandler { 196 return func(sourceObject Object, reader io.Reader, destinationService Service, destinationURL string) error { 197 if !predicate(sourceObject) { 198 return nil 199 } 200 var _, relativePath = toolbox.URLSplit(destinationURL) 201 if destinationURL != parentURL { 202 relativePath = strings.Replace(destinationURL, parentURL, "", 1) 203 } 204 header, err := zip.FileInfoHeader(sourceObject.FileInfo()) 205 if err != nil { 206 return err 207 } 208 header.Method = zip.Store 209 210 if strings.HasPrefix(relativePath, "/") { 211 relativePath = string(relativePath[1:]) 212 } 213 header.Name = relativePath 214 writer, err := archive.CreateHeader(header) 215 if err != nil { 216 return err 217 } 218 _, err = io.Copy(writer, reader) 219 return err 220 } 221 } 222 223 //Archive archives supplied URL assets into zip writer with supplied filter 224 func ArchiveWithFilter(service Service, URL string, writer *zip.Writer, predicate func(candidate Object) bool) error { 225 memService := NewMemoryService() 226 var destURL = "mem:///dev/nul" 227 return Copy(service, URL, memService, destURL, nil, getArchiveCopyHandlerWithFilter(writer, destURL, predicate)) 228 } 229 230 func getTarCopyHandler(archive *tar.Writer, destParentURL, parentURL string, dirs map[string]bool) CopyHandler { 231 if strings.HasSuffix(parentURL, "/") { 232 parentURL = string(parentURL[:len(parentURL)-2]) 233 } 234 _, root := path.Split(destParentURL) 235 if root == "." { 236 root = "" 237 } 238 return func(sourceObject Object, reader io.Reader, destinationService Service, destinationURL string) error { 239 var _, relativePath = toolbox.URLSplit(destinationURL) 240 if destinationURL != parentURL { 241 relativePath = strings.Replace(destinationURL, parentURL, "", 1) 242 } 243 244 if strings.HasPrefix(relativePath, "/") { 245 relativePath = string(relativePath[1:]) 246 } 247 248 relativePath = path.Join(root, relativePath) 249 parent, _ := path.Split(relativePath) 250 251 if parent != "" && !dirs[parent] { 252 tarHeader := &tar.Header{ 253 Name: parent, 254 Size: int64(0), 255 Mode: int64(sourceObject.FileInfo().Mode()), 256 ModTime: sourceObject.FileInfo().ModTime(), 257 } 258 if err := archive.WriteHeader(tarHeader); err != nil { 259 return fmt.Errorf(" unable to write tar header, %v", err) 260 } 261 dirs[parent] = true 262 } 263 264 contents := new(bytes.Buffer) 265 if _, err := io.Copy(contents, reader); err != nil { 266 return err 267 } 268 data := contents.Bytes() 269 tarHeader := &tar.Header{ 270 Name: relativePath, 271 Size: int64(len(data)), 272 Mode: int64(sourceObject.FileInfo().Mode()), 273 ModTime: sourceObject.FileInfo().ModTime(), 274 } 275 if err := archive.WriteHeader(tarHeader); err != nil { 276 return fmt.Errorf(" unable to write tar header, %v", err) 277 } 278 if _, err := archive.Write(data); err != nil { 279 return fmt.Errorf(" unable to write tar content, %v", err) 280 } 281 return nil 282 } 283 } 284 285 //Tar tar archives supplied URL assets into zip writer 286 func Tar(service Service, URL string, writer *tar.Writer, includeOwnerDir bool) error { 287 memService := NewMemoryService() 288 var destURL = "mem:///dev/nul" 289 var dirs = make(map[string]bool) 290 ownerDir := "" 291 if includeOwnerDir { 292 ownerDir = URL 293 } 294 return Copy(service, URL, memService, destURL, nil, getTarCopyHandler(writer, ownerDir, destURL, dirs)) 295 }