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 }