package storage import ( "io" "os" "path/filepath" ) // Storage defines the interface for object storage operations type Storage interface { // Bucket operations CreateBucket(name string) error DeleteBucket(name string) error BucketExists(name string) (bool, error) ListBuckets() ([]BucketInfo, error) // Object operations PutObject(bucket, key string, reader io.Reader) error GetObject(bucket, key string) (io.ReadCloser, *ObjectInfo, error) DeleteObject(bucket, key string) error ObjectExists(bucket, key string) (bool, error) GetObjectInfo(bucket, key string) (*ObjectInfo, error) ListObjects(bucket string) ([]ObjectInfo, error) } // FileSystemStorage implements Storage using the local file system type FileSystemStorage struct { baseDir string } // NewFileSystemStorage creates a new file system storage instance func NewFileSystemStorage(baseDir string) (*FileSystemStorage, error) { if err := os.MkdirAll(baseDir, 0755); err != nil { return nil, err } return &FileSystemStorage{ baseDir: baseDir, }, nil } // CreateBucket creates a new bucket directory func (fs *FileSystemStorage) CreateBucket(name string) error { bucketPath := filepath.Join(fs.baseDir, name) // Check if bucket already exists if _, err := os.Stat(bucketPath); err == nil { return ErrBucketAlreadyExists } return os.MkdirAll(bucketPath, 0755) } // DeleteBucket removes a bucket directory func (fs *FileSystemStorage) DeleteBucket(name string) error { bucketPath := filepath.Join(fs.baseDir, name) return os.RemoveAll(bucketPath) } // BucketExists checks if a bucket exists func (fs *FileSystemStorage) BucketExists(name string) (bool, error) { bucketPath := filepath.Join(fs.baseDir, name) _, err := os.Stat(bucketPath) if os.IsNotExist(err) { return false, nil } if err != nil { return false, err } return true, nil } // ListBuckets lists all buckets func (fs *FileSystemStorage) ListBuckets() ([]BucketInfo, error) { entries, err := os.ReadDir(fs.baseDir) if err != nil { return nil, err } var buckets []BucketInfo for _, entry := range entries { if entry.IsDir() { info, err := entry.Info() if err != nil { continue } buckets = append(buckets, BucketInfo{ Name: entry.Name(), CreationDate: info.ModTime(), }) } } return buckets, nil } // PutObject stores an object func (fs *FileSystemStorage) PutObject(bucket, key string, reader io.Reader) error { bucketPath := filepath.Join(fs.baseDir, bucket) // Check if bucket exists if _, err := os.Stat(bucketPath); os.IsNotExist(err) { return ErrBucketNotFound } objectPath := filepath.Join(bucketPath, key) // Create parent directories if needed objectDir := filepath.Dir(objectPath) if err := os.MkdirAll(objectDir, 0755); err != nil { return err } // Create the file file, err := os.Create(objectPath) if err != nil { return err } defer file.Close() // Copy data to file _, err = io.Copy(file, reader) return err } // GetObject retrieves an object func (fs *FileSystemStorage) GetObject(bucket, key string) (io.ReadCloser, *ObjectInfo, error) { objectPath := filepath.Join(fs.baseDir, bucket, key) fileInfo, err := os.Stat(objectPath) if os.IsNotExist(err) { return nil, nil, ErrObjectNotFound } if err != nil { return nil, nil, err } file, err := os.Open(objectPath) if err != nil { return nil, nil, err } info := &ObjectInfo{ Key: key, Size: fileInfo.Size(), LastModified: fileInfo.ModTime(), ETag: generateETag(fileInfo), } return file, info, nil } // DeleteObject removes an object func (fs *FileSystemStorage) DeleteObject(bucket, key string) error { objectPath := filepath.Join(fs.baseDir, bucket, key) err := os.Remove(objectPath) if os.IsNotExist(err) { return nil // S3 returns success even if object doesn't exist } return err } // ObjectExists checks if an object exists func (fs *FileSystemStorage) ObjectExists(bucket, key string) (bool, error) { objectPath := filepath.Join(fs.baseDir, bucket, key) _, err := os.Stat(objectPath) if os.IsNotExist(err) { return false, nil } if err != nil { return false, err } return true, nil } // GetObjectInfo retrieves object metadata func (fs *FileSystemStorage) GetObjectInfo(bucket, key string) (*ObjectInfo, error) { objectPath := filepath.Join(fs.baseDir, bucket, key) fileInfo, err := os.Stat(objectPath) if os.IsNotExist(err) { return nil, ErrObjectNotFound } if err != nil { return nil, err } return &ObjectInfo{ Key: key, Size: fileInfo.Size(), LastModified: fileInfo.ModTime(), ETag: generateETag(fileInfo), }, nil } // ListObjects lists all objects in a bucket func (fs *FileSystemStorage) ListObjects(bucket string) ([]ObjectInfo, error) { bucketPath := filepath.Join(fs.baseDir, bucket) if _, err := os.Stat(bucketPath); os.IsNotExist(err) { return nil, ErrBucketNotFound } entries, err := os.ReadDir(bucketPath) if err != nil { return nil, err } var objects []ObjectInfo for _, entry := range entries { if !entry.IsDir() { info, err := entry.Info() if err != nil { continue } objects = append(objects, ObjectInfo{ Key: entry.Name(), Size: info.Size(), LastModified: info.ModTime(), ETag: generateETag(info), }) } } return objects, nil }