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  }