github.com/cyverse/go-irodsclient@v0.13.2/irods/fs/transfer_status.go (about)

     1  package fs
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/cyverse/go-irodsclient/irods/util"
    13  	"golang.org/x/xerrors"
    14  )
    15  
    16  const (
    17  	DataObjectTransferStatusFilePrefix string = ".grc."
    18  	DataObjectTransferStatusFileSuffix string = ".trx_status"
    19  )
    20  
    21  // DataObjectTransferStatusEntry
    22  type DataObjectTransferStatusEntry struct {
    23  	StartOffset     int64 `json:"start_offset"`
    24  	Length          int64 `json:"length"`
    25  	CompletedLength int64 `json:"completed_length"`
    26  }
    27  
    28  // DataObjectTransferStatus represents data object transfer
    29  type DataObjectTransferStatus struct {
    30  	Path           string                                   `json:"path"`
    31  	StatusFilePath string                                   `json:"status_file_path"`
    32  	Size           int64                                    `json:"size"`
    33  	Threads        int                                      `json:"threads"`
    34  	StatusMap      map[int64]*DataObjectTransferStatusEntry `json:"-"`
    35  }
    36  
    37  func (status *DataObjectTransferStatus) Validate(path string, size int64) bool {
    38  	if status.Path != path {
    39  		return false
    40  	}
    41  
    42  	if status.Size != size {
    43  		return false
    44  	}
    45  
    46  	return true
    47  }
    48  
    49  // IsDataObjectTransferStatusFile checks if the file is transfer status file
    50  func IsDataObjectTransferStatusFile(p string) bool {
    51  	filename := util.GetBasename(p)
    52  	return strings.HasPrefix(filename, DataObjectTransferStatusFilePrefix) && strings.HasSuffix(filename, DataObjectTransferStatusFileSuffix)
    53  }
    54  
    55  // GetDataObjectTransferStatusFilePath returns transfer status file path
    56  func GetDataObjectTransferStatusFilePath(p string) string {
    57  	dir := util.GetDir(p)
    58  	filename := util.GetBasename(p)
    59  	if strings.HasPrefix(filename, DataObjectTransferStatusFilePrefix) && strings.HasSuffix(filename, DataObjectTransferStatusFileSuffix) {
    60  		// p is status file
    61  		return p
    62  	}
    63  
    64  	statusFilename := fmt.Sprintf("%s%s%s", DataObjectTransferStatusFilePrefix, filename, DataObjectTransferStatusFileSuffix)
    65  	return util.Join(dir, statusFilename)
    66  }
    67  
    68  // NewDataObjectTransferStatus creates new DataObjectTransferStatus
    69  func NewDataObjectTransferStatus(path string, size int64, threads int) *DataObjectTransferStatus {
    70  	return &DataObjectTransferStatus{
    71  		Path:           path,
    72  		StatusFilePath: GetDataObjectTransferStatusFilePath(path),
    73  		Size:           size,
    74  		Threads:        threads,
    75  		StatusMap:      map[int64]*DataObjectTransferStatusEntry{},
    76  	}
    77  }
    78  
    79  func newDataObjectTransferFromBytes(data []byte) (*DataObjectTransferStatus, error) {
    80  	byteReader := bytes.NewReader(data)
    81  	bufReader := bufio.NewReader(byteReader)
    82  	line, prefix, err := bufReader.ReadLine()
    83  	if err != nil {
    84  		return nil, xerrors.Errorf("failed to read lines from bytedata: %w", err)
    85  	}
    86  
    87  	if prefix {
    88  		return nil, xerrors.Errorf("failed to read long line from bytedata, buffer overflow")
    89  	}
    90  
    91  	// first line is status
    92  	transferStatus := DataObjectTransferStatus{}
    93  	err = json.Unmarshal(line, &transferStatus)
    94  	if err != nil {
    95  		return nil, xerrors.Errorf("failed to unmarshal json data to DataObjectTransferStatus: %w", err)
    96  	}
    97  
    98  	statusMap := map[int64]*DataObjectTransferStatusEntry{}
    99  
   100  	for {
   101  		line, prefix, err := bufReader.ReadLine()
   102  		if err != nil {
   103  			if err == io.EOF {
   104  				break
   105  			}
   106  
   107  			return nil, xerrors.Errorf("failed to read lines from bytedata: %w", err)
   108  		}
   109  
   110  		if prefix {
   111  			return nil, xerrors.Errorf("failed to read long line from bytedata, buffer overflow")
   112  		}
   113  
   114  		if len(line) > 0 {
   115  			statusEntry := DataObjectTransferStatusEntry{}
   116  			err = json.Unmarshal(line, &statusEntry)
   117  			if err != nil {
   118  				return nil, xerrors.Errorf("failed to unmarshal json data to DataObjectTransferStatusEntry: %w", err)
   119  			}
   120  
   121  			// update
   122  			statusMap[statusEntry.StartOffset] = &statusEntry
   123  		}
   124  	}
   125  
   126  	transferStatus.StatusMap = statusMap
   127  	return &transferStatus, nil
   128  }
   129  
   130  type DataObjectTransferStatusLocal struct {
   131  	status     *DataObjectTransferStatus
   132  	fileHandle *os.File
   133  }
   134  
   135  func NewDataObjectTransferStatusLocal(localPath string, size int64, threads int) *DataObjectTransferStatusLocal {
   136  	status := NewDataObjectTransferStatus(localPath, size, threads)
   137  
   138  	return &DataObjectTransferStatusLocal{
   139  		status:     status,
   140  		fileHandle: nil,
   141  	}
   142  }
   143  
   144  func (status *DataObjectTransferStatusLocal) GetStatus() *DataObjectTransferStatus {
   145  	return status.status
   146  }
   147  
   148  func (status *DataObjectTransferStatusLocal) CreateStatusFile() error {
   149  	handle, err := os.Create(status.status.StatusFilePath)
   150  	if err != nil {
   151  		return xerrors.Errorf("failed to create file %s: %w", status.status.StatusFilePath, err)
   152  	}
   153  
   154  	status.fileHandle = handle
   155  	return nil
   156  }
   157  
   158  func (status *DataObjectTransferStatusLocal) CloseStatusFile() error {
   159  	var err error
   160  	if status.fileHandle != nil {
   161  		err = status.fileHandle.Close()
   162  		status.fileHandle = nil
   163  	}
   164  
   165  	return err
   166  }
   167  
   168  func (status *DataObjectTransferStatusLocal) DeleteStatusFile() error {
   169  	err := os.RemoveAll(status.status.StatusFilePath)
   170  	if err != nil {
   171  		return xerrors.Errorf("failed to delete status file %s: %w", status.status.StatusFilePath, err)
   172  	}
   173  
   174  	return nil
   175  }
   176  
   177  func (status *DataObjectTransferStatusLocal) WriteHeader() error {
   178  	if status.fileHandle == nil {
   179  		return xerrors.Errorf("failed to write header, file handle is nil")
   180  	}
   181  
   182  	bytes, err := json.Marshal(status.status)
   183  	if err != nil {
   184  		return xerrors.Errorf("failed to marshal DataObjectTransferStatus to json: %w", err)
   185  	}
   186  
   187  	bytes = append(bytes, '\n')
   188  	_, err = status.fileHandle.Write(bytes)
   189  	return err
   190  }
   191  
   192  func (status *DataObjectTransferStatusLocal) WriteStatus(entry *DataObjectTransferStatusEntry) error {
   193  	bytes, err := json.Marshal(entry)
   194  	if err != nil {
   195  		return xerrors.Errorf("failed to marshal DataObjectTransferStatusEntry to json: %w", err)
   196  	}
   197  
   198  	bytes = append(bytes, '\n')
   199  	_, err = status.fileHandle.Write(bytes)
   200  	return err
   201  }
   202  
   203  // GetDataObjectTransferStatusLocal returns DataObjectTransferStatusLocal in local disk
   204  func GetDataObjectTransferStatusLocal(localPath string) (*DataObjectTransferStatusLocal, error) {
   205  	statusFilePath := GetDataObjectTransferStatusFilePath(localPath)
   206  
   207  	_, err := os.Stat(statusFilePath)
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  
   212  	data, err := os.ReadFile(statusFilePath)
   213  	if err != nil {
   214  		return nil, xerrors.Errorf("failed to read file %s: %w", statusFilePath, err)
   215  	}
   216  
   217  	status, err := newDataObjectTransferFromBytes(data)
   218  	if err != nil {
   219  		return nil, xerrors.Errorf("failed to create transfer status for %s: %w", localPath, err)
   220  	}
   221  
   222  	return &DataObjectTransferStatusLocal{
   223  		status:     status,
   224  		fileHandle: nil,
   225  	}, nil
   226  }
   227  
   228  // GetOrNewDataObjectTransferStatusLocal returns DataObjectTransferStatus in iRODS
   229  func GetOrNewDataObjectTransferStatusLocal(localPath string, size int64, threads int) (*DataObjectTransferStatusLocal, error) {
   230  	status, err := GetDataObjectTransferStatusLocal(localPath)
   231  	if err != nil {
   232  		if os.IsNotExist(err) {
   233  			// status file not found
   234  			status := NewDataObjectTransferStatusLocal(localPath, size, threads)
   235  			return status, nil
   236  		}
   237  
   238  		return nil, xerrors.Errorf("failed to read transfer status for %s: %w", localPath, err)
   239  	}
   240  
   241  	if !status.status.Validate(localPath, size) {
   242  		// cannot reuse, create a new
   243  		status := NewDataObjectTransferStatusLocal(localPath, size, threads)
   244  		return status, nil
   245  	}
   246  
   247  	return status, nil
   248  }