/* * Copyright 2026 Oleg Borodin */ package storage import ( "bytes" "errors" "io" "os" "path/filepath" ) const ( upsubdir = "uploads" blobsubdir = "blobs" ) func (store *Storage) makeUppath(upname string) string { return filepath.Join(store.basepath, upsubdir, upname) + ".bin" } func (store *Storage) makeUpsubdir() string { return filepath.Join(store.basepath, upsubdir) } func (store *Storage) makeBlobpath(name, digstr string) string { return filepath.Join(store.basepath, blobsubdir, name, digstr) + ".bin" } func (store *Storage) makeBlobsubdir(name string) string { return filepath.Join(store.basepath, blobsubdir, name) } func (store *Storage) WriteUpload(upID string, source io.Reader) (int64, error) { var err error var recsize int64 uploadDir := store.makeUpsubdir() _, err = os.Stat(uploadDir) if os.IsNotExist(err) { err = os.MkdirAll(uploadDir, 0750) if err != nil { return recsize, err } } if err != nil { return recsize, err } upPath := store.makeUppath(upID) upFile, err := os.OpenFile(upPath, os.O_WRONLY|os.O_CREATE, 0644) if err != nil { return recsize, err } defer upFile.Close() recsize, err = io.Copy(upFile, source) if err != nil { return recsize, err } return recsize, err } func (store *Storage) LinkUpload(reference, name, digest string) error { var err error upPath := store.makeUppath(reference) blobdir := store.makeBlobsubdir(name) _, err = os.Stat(blobdir) if errors.Is(err, os.ErrNotExist) { err = os.MkdirAll(blobdir, 0750) if err != nil { return err } } if err != nil { return err } blobPath := store.makeBlobpath(name, digest) _, err = os.Stat(blobPath) if err == nil { err = os.Remove(blobPath) if err != nil { return err } } if os.IsNotExist(err) { err = nil } if err != nil { return err } err = os.Link(upPath, blobPath) if err != nil { return err } err = os.Remove(upPath) if err != nil { return err } return err } func (store *Storage) RemoveUpload(digest string) error { var err error upPath := store.makeUppath(digest) err = os.Remove(upPath) if err != nil { return err } return err } func (st *Storage) UploadExists(name, reference string) (bool, int64, error) { var err error var size int64 upPath := st.makeUppath(reference) fileStat, err := os.Stat(upPath) if errors.Is(err, os.ErrNotExist) { return false, 0, nil } if err != nil { return false, 0, err } size = fileStat.Size() return true, size, err } func (store *Storage) WriteBlob(name, digstr string, source io.Reader) (int64, error) { var err error var size int64 blobDir := store.makeBlobsubdir(name) _, err = os.Stat(blobDir) if os.IsNotExist(err) { err = os.MkdirAll(blobDir, 0750) if err != nil { return size, err } } if err != nil { return size, err } blobPath := store.makeBlobpath(name, digstr) blobFile, err := os.OpenFile(blobPath, os.O_WRONLY|os.O_CREATE, 0644) if err != nil { return size, err } defer blobFile.Close() size, err = io.Copy(blobFile, source) if err != nil { return size, err } return size, err } func (st *Storage) BlobExists(name, digest string) (bool, int64, error) { var err error var size int64 blobPath := st.makeBlobpath(name, digest) fileStat, err := os.Stat(blobPath) if os.IsNotExist(err) { return false, 0, nil } if err != nil { return false, 0, err } size = fileStat.Size() return true, size, err } func (store *Storage) BlobReader(name, digest string) (int64, io.ReadCloser, error) { var err error var size int64 blobPath := store.makeBlobpath(name, digest) nop := io.NopCloser(bytes.NewReader(nil)) file, err := os.OpenFile(blobPath, os.O_RDONLY, 0) if err != nil { return size, nop, err } defer func() { if err != nil { file.Close() } }() filestat, err := file.Stat() if err != nil { return size, nop, err } size = filestat.Size() return size, file, err } func (store *Storage) DeleteBlob(name, digest string) error { var err error blobPath := store.makeBlobpath(name, digest) err = os.Remove(blobPath) if os.IsNotExist(err) { err = nil } if err != nil { return err } // TODO: remove dirs return err }