go-hep.org/x/hep@v0.38.1/xrootd/xrdio/xrdio.go (about)

     1  // Copyright ©2018 The go-hep Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package xrdio provides a File type that implements various interfaces from the io package.
     6  package xrdio // import "go-hep.org/x/hep/xrootd/xrdio"
     7  
     8  import (
     9  	"context"
    10  	"fmt"
    11  	"io"
    12  	"io/fs"
    13  	"os"
    14  
    15  	"go-hep.org/x/hep/xrootd"
    16  	"go-hep.org/x/hep/xrootd/xrdfs"
    17  )
    18  
    19  // File wraps a xrdfs.File and implements the following interfaces:
    20  //   - io.Closer
    21  //   - io.Reader
    22  //   - io.Writer
    23  //   - io.ReaderAt
    24  //   - io.WriterAt
    25  //   - io.Seeker
    26  //   - fs.File
    27  type File struct {
    28  	cli *xrootd.Client
    29  	fs  xrdfs.FileSystem
    30  	f   xrdfs.File
    31  
    32  	name string
    33  	pos  int64
    34  	size int64
    35  }
    36  
    37  // Open opens the name file, where name is the absolute location of that file
    38  // (xrootd server address and path to the file on that server.)
    39  //
    40  // Example:
    41  //
    42  //	f, err := xrdio.Open("root://server.example.com:1094//some/path/to/file")
    43  func Open(name string) (*File, error) {
    44  	urn, err := Parse(name)
    45  	if err != nil {
    46  		return nil, fmt.Errorf("could not parse %q: %w", name, err)
    47  	}
    48  
    49  	xrd, err := xrootd.NewClient(context.Background(), urn.Addr, urn.User)
    50  	if err != nil {
    51  		return nil, fmt.Errorf("xrdio: could not connect to xrootd server %q: %w", urn.Addr, err)
    52  	}
    53  
    54  	fs := xrd.FS()
    55  	f, err := fs.Open(context.Background(), urn.Path, xrdfs.OpenModeOwnerRead, xrdfs.OpenOptionsOpenRead)
    56  	if err != nil {
    57  		xrd.Close()
    58  		return nil, fmt.Errorf("xrdio: could not open %q: %w", name, err)
    59  	}
    60  
    61  	xf := &File{cli: xrd, fs: fs, f: f, name: urn.Path}
    62  	fi, err := xf.Stat()
    63  	if err != nil {
    64  		xrd.Close()
    65  		return nil, fmt.Errorf("xrdio: could not stat %q: %w", name, err)
    66  	}
    67  	xf.size = fi.Size()
    68  
    69  	return xf, nil
    70  }
    71  
    72  // OpenFrom opens the file name via the given filesystem handle.
    73  // name is the absolute path of the wanted file on the server.
    74  //
    75  // Example:
    76  //
    77  //	f, err := xrdio.OpenFrom(fs, "/some/path/to/file")
    78  func OpenFrom(fs xrdfs.FileSystem, name string) (*File, error) {
    79  	f, err := fs.Open(context.Background(), name, xrdfs.OpenModeOwnerRead, xrdfs.OpenOptionsOpenRead)
    80  	if err != nil {
    81  		return nil, fmt.Errorf("xrdio: could not open %q: %w", name, err)
    82  	}
    83  
    84  	xf := &File{fs: fs, f: f, name: name}
    85  	fi, err := xf.Stat()
    86  	if err != nil {
    87  		return nil, fmt.Errorf("xrdio: could not stat %q: %w", name, err)
    88  	}
    89  	xf.size = fi.Size()
    90  
    91  	return xf, nil
    92  }
    93  
    94  // Name returns the name of the file.
    95  func (f *File) Name() string {
    96  	return f.name
    97  }
    98  
    99  // Close implements io.Closer.
   100  func (f *File) Close() error {
   101  	if f == nil {
   102  		return os.ErrInvalid
   103  	}
   104  
   105  	var (
   106  		err1 = f.f.Close(context.Background())
   107  		err2 error
   108  	)
   109  
   110  	if f.cli != nil {
   111  		err2 = f.cli.Close()
   112  	}
   113  	if err1 != nil {
   114  		return fmt.Errorf("xrdio: could not close file %q: %w", f.name, err1)
   115  	}
   116  	if err2 != nil {
   117  		return fmt.Errorf("xrdio: could not close xrd-client: %w", err2)
   118  	}
   119  	return nil
   120  }
   121  
   122  // Read implements io.Reader.
   123  func (f *File) Read(data []byte) (int, error) {
   124  	n, err := f.f.ReadAt(data, f.pos)
   125  	f.pos += int64(n)
   126  	if err != nil {
   127  		return n, err
   128  	}
   129  	if f.pos == f.size {
   130  		err = io.EOF
   131  	}
   132  	return n, err
   133  }
   134  
   135  // ReadAt implements io.ReaderAt.
   136  func (f *File) ReadAt(data []byte, offset int64) (int, error) {
   137  	return f.f.ReadAt(data, offset)
   138  }
   139  
   140  // Write implements io.Writer.
   141  func (f *File) Write(data []byte) (int, error) {
   142  	n, err := f.f.WriteAt(data, f.pos)
   143  	f.pos += int64(n)
   144  	return n, err
   145  }
   146  
   147  // WriteAt implements io.WriterAt.
   148  func (f *File) WriteAt(data []byte, offset int64) (int, error) {
   149  	return f.f.WriteAt(data, offset)
   150  }
   151  
   152  // Seek implements io.Seeker
   153  func (f *File) Seek(offset int64, whence int) (int64, error) {
   154  	var err error
   155  	switch whence {
   156  	case io.SeekStart:
   157  		f.pos = offset
   158  	case io.SeekEnd:
   159  		st, err := f.Stat()
   160  		if err != nil {
   161  			return 0, fmt.Errorf("xrdio: could not xrootd-stat %q: %w", f.Name(), err)
   162  		}
   163  		f.pos = st.Size() - offset
   164  	case io.SeekCurrent:
   165  		f.pos += offset
   166  	}
   167  	return f.pos, err
   168  }
   169  
   170  func (f *File) Stat() (os.FileInfo, error) {
   171  	v, err := f.f.Stat(context.Background())
   172  	return v, err
   173  }
   174  
   175  var (
   176  	_ io.Closer   = (*File)(nil)
   177  	_ io.Reader   = (*File)(nil)
   178  	_ io.ReaderAt = (*File)(nil)
   179  	_ io.Writer   = (*File)(nil)
   180  	_ io.WriterAt = (*File)(nil)
   181  	_ io.Seeker   = (*File)(nil)
   182  	_ fs.File     = (*File)(nil)
   183  )