github.com/enetx/g@v1.0.80/file.go (about) 1 package g 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "io/fs" 8 "net/http" 9 "os" 10 "path/filepath" 11 12 "github.com/enetx/g/internal/filelock" 13 ) 14 15 // NewFile returns a new File instance with the given name. 16 func NewFile(name String) *File { return &File{name: name} } 17 18 // Lines returns a new iterator instance that can be used to read the file 19 // line by line. 20 // 21 // Example usage: 22 // 23 // // Open a new file with the specified name "text.txt" 24 // g.NewFile("text.txt"). 25 // Lines(). // Read the file line by line 26 // Unwrap(). // Unwrap the Result type to get the underlying iterator 27 // Skip(3). // Skip the first 3 lines 28 // Exclude(f.Zero). // Exclude lines that are empty or contain only whitespaces 29 // Dedup(). // Remove consecutive duplicate lines 30 // Map(g.String.Upper). // Convert each line to uppercase 31 // ForEach( // For each line, print it 32 // func(s g.String) { 33 // s.Print() 34 // }) 35 // 36 // // Output: 37 // // UPPERCASED_LINE4 38 // // UPPERCASED_LINE5 39 // // UPPERCASED_LINE6 40 func (f *File) Lines() Result[SeqSlice[String]] { 41 if f.file == nil { 42 if r := f.Open(); r.IsErr() { 43 return Err[SeqSlice[String]](r.Err()) 44 } 45 } 46 47 return Ok(SeqSlice[String](func(yield func(String) bool) { 48 defer f.Close() 49 50 scanner := bufio.NewScanner(f.file) 51 scanner.Split(bufio.ScanLines) 52 53 for scanner.Scan() { 54 if !yield(String(scanner.Text())) { 55 return 56 } 57 } 58 })) 59 } 60 61 // Chunks returns a new iterator instance that can be used to read the file 62 // in fixed-size chunks of the specified size in bytes. 63 // 64 // Parameters: 65 // 66 // - size (int): The size of each chunk in bytes. 67 // 68 // Example usage: 69 // 70 // // Open a new file with the specified name "text.txt" 71 // g.NewFile("text.txt"). 72 // Chunks(100). // Read the file in chunks of 100 bytes 73 // Unwrap(). // Unwrap the Result type to get the underlying iterator 74 // Map(g.String.Upper). // Convert each chunk to uppercase 75 // ForEach( // For each chunk, print it 76 // func(s g.String) { 77 // s.Print() 78 // }) 79 // 80 // // Output: 81 // // UPPERCASED_CHUNK1 82 // // UPPERCASED_CHUNK2 83 // // UPPERCASED_CHUNK3 84 func (f *File) Chunks(size Int) Result[SeqSlice[String]] { 85 if f.file == nil { 86 if r := f.Open(); r.IsErr() { 87 return Err[SeqSlice[String]](r.Err()) 88 } 89 } 90 91 return Ok(SeqSlice[String](func(yield func(String) bool) { 92 defer f.Close() 93 94 buffer := make([]byte, size) 95 96 for { 97 n, err := f.file.Read(buffer) 98 if err != nil && err != io.EOF { 99 return 100 } 101 102 if n == 0 { 103 break 104 } 105 106 if !yield(String(buffer[:n])) { 107 return 108 } 109 } 110 })) 111 } 112 113 // Append appends the given content to the file, with the specified mode (optional). 114 // If no FileMode is provided, the default FileMode (0644) is used. 115 // Don't forget to close the file! 116 func (f *File) Append(content String, mode ...os.FileMode) Result[*File] { 117 if f.file == nil { 118 if r := f.createAll(); r.IsErr() { 119 return r 120 } 121 122 fmode := FileDefault 123 if len(mode) != 0 { 124 fmode = mode[0] 125 } 126 127 if r := f.OpenFile(os.O_APPEND|os.O_CREATE|os.O_WRONLY, fmode); r.IsErr() { 128 return r 129 } 130 } 131 132 if _, err := f.file.WriteString(content.Std()); err != nil { 133 return Err[*File](err) 134 } 135 136 return Ok(f) 137 } 138 139 // Chmod changes the mode of the file. 140 func (f *File) Chmod(mode os.FileMode) Result[*File] { 141 var err error 142 if f.file != nil { 143 err = f.file.Chmod(mode) 144 } else { 145 err = os.Chmod(f.name.Std(), mode) 146 } 147 148 if err != nil { 149 return Err[*File](err) 150 } 151 152 return Ok(f) 153 } 154 155 // Chown changes the owner of the file. 156 func (f *File) Chown(uid, gid int) Result[*File] { 157 var err error 158 if f.file != nil { 159 err = f.file.Chown(uid, gid) 160 } else { 161 err = os.Chown(f.name.Std(), uid, gid) 162 } 163 164 if err != nil { 165 return Err[*File](err) 166 } 167 168 return Ok(f) 169 } 170 171 // Seek sets the file offset for the next Read or Write operation. The offset 172 // is specified by the 'offset' parameter, and the 'whence' parameter determines 173 // the reference point for the offset. 174 // 175 // The 'offset' parameter specifies the new offset in bytes relative to the 176 // reference point determined by 'whence'. If 'whence' is set to io.SeekStart, 177 // io.SeekCurrent, or io.SeekEnd, the offset is relative to the start of the file, 178 // the current offset, or the end of the file, respectively. 179 // 180 // If the file is not open, this method will attempt to open it. If the open 181 // operation fails, an error is returned. 182 // 183 // If the Seek operation fails, the file is closed, and an error is returned. 184 // 185 // Example: 186 // 187 // file := g.NewFile("example.txt") 188 // result := file.Seek(100, io.SeekStart) 189 // if result.Err() != nil { 190 // log.Fatal(result.Err()) 191 // } 192 // 193 // Parameters: 194 // - offset: The new offset in bytes. 195 // - whence: The reference point for the offset (io.SeekStart, io.SeekCurrent, or io.SeekEnd). 196 // 197 // Don't forget to close the file! 198 func (f *File) Seek(offset int64, whence int) Result[*File] { 199 if f.file == nil { 200 if r := f.Open(); r.IsErr() { 201 return r 202 } 203 } 204 205 if _, err := f.file.Seek(offset, whence); err != nil { 206 f.Close() 207 return Err[*File](err) 208 } 209 210 return Ok(f) 211 } 212 213 // Close closes the File and unlocks its underlying file, if it is not already closed. 214 func (f *File) Close() error { 215 if f.file == nil { 216 return &ErrFileClosed{f.name.Std()} 217 } 218 219 var err error 220 221 if f.guard { 222 err = filelock.Unlock(f.file) 223 } 224 225 if closeErr := f.file.Close(); closeErr != nil { 226 err = closeErr 227 } 228 229 f.file = nil 230 231 return err 232 } 233 234 // Copy copies the file to the specified destination, with the specified mode (optional). 235 // If no mode is provided, the default FileMode (0644) is used. 236 func (f *File) Copy(dest String, mode ...os.FileMode) Result[*File] { 237 if r := f.Open(); r.IsErr() { 238 return r 239 } 240 241 defer f.Close() 242 243 return NewFile(dest).WriteFromReader(f.file, mode...) 244 } 245 246 // Create is similar to os.Create, but it returns a write-locked file. 247 // Don't forget to close the file! 248 func (f *File) Create() Result[*File] { 249 return f.OpenFile(os.O_RDWR|os.O_CREATE|os.O_TRUNC, FileCreate) 250 } 251 252 // Dir returns the directory the file is in as an Dir instance. 253 func (f *File) Dir() Result[*Dir] { 254 dirPath := f.dirPath() 255 if dirPath.IsErr() { 256 return Err[*Dir](dirPath.Err()) 257 } 258 259 return Ok(NewDir(dirPath.Ok())) 260 } 261 262 // Exist checks if the file exists. 263 func (f *File) Exist() bool { 264 if f.dirPath().IsOk() { 265 filePath := f.filePath() 266 if filePath.IsOk() { 267 _, err := os.Stat(filePath.Ok().Std()) 268 return !os.IsNotExist(err) 269 } 270 } 271 272 return false 273 } 274 275 // Ext returns the file extension. 276 func (f *File) Ext() String { return String(filepath.Ext(f.name.Std())) } 277 278 // Guard sets a lock on the file to protect it from concurrent access. 279 // It returns the File instance with the guard enabled. 280 func (f *File) Guard() *File { 281 f.guard = true 282 return f 283 } 284 285 // MimeType returns the MIME type of the file as an String. 286 func (f *File) MimeType() Result[String] { 287 if r := f.Open(); r.IsErr() { 288 return Err[String](r.Err()) 289 } 290 291 defer f.Close() 292 293 const bufferSize = 512 294 295 buff := make([]byte, bufferSize) 296 297 bytesRead, err := f.file.ReadAt(buff, 0) 298 if err != nil && err != io.EOF { 299 return Err[String](err) 300 } 301 302 buff = buff[:bytesRead] 303 304 return Ok(String(http.DetectContentType(buff))) 305 } 306 307 // Move function simply calls [File.Rename] 308 func (f *File) Move(newpath String) Result[*File] { return f.Rename(newpath) } 309 310 // Name returns the name of the file. 311 func (f *File) Name() String { 312 if f.file != nil { 313 return String(filepath.Base(f.file.Name())) 314 } 315 316 return String(filepath.Base(f.name.Std())) 317 } 318 319 // Open is like os.Open, but returns a read-locked file. 320 // Don't forget to close the file! 321 func (f *File) Open() Result[*File] { return f.OpenFile(os.O_RDONLY, 0) } 322 323 // OpenFile is like os.OpenFile, but returns a locked file. 324 // If flag includes os.O_WRONLY or os.O_RDWR, the file is write-locked 325 // otherwise, it is read-locked. 326 // Don't forget to close the file! 327 func (f *File) OpenFile(flag int, perm fs.FileMode) Result[*File] { 328 file, err := os.OpenFile(f.name.Std(), flag&^os.O_TRUNC, perm) 329 if err != nil { 330 return Err[*File](err) 331 } 332 333 if f.guard { 334 switch flag & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR) { 335 case os.O_WRONLY, os.O_RDWR: 336 err = filelock.Lock(file) 337 default: 338 err = filelock.RLock(file) 339 } 340 341 if err != nil { 342 file.Close() 343 return Err[*File](err) 344 } 345 } 346 347 if flag&os.O_TRUNC == os.O_TRUNC { 348 if err := file.Truncate(0); err != nil { 349 if fi, statErr := file.Stat(); statErr != nil || fi.Mode().IsRegular() { 350 if f.guard { 351 filelock.Unlock(file) 352 } 353 354 file.Close() 355 356 return Err[*File](err) 357 } 358 } 359 } 360 361 f.file = file 362 363 return Ok(f) 364 } 365 366 // Path returns the absolute path of the file. 367 func (f *File) Path() Result[String] { return f.filePath() } 368 369 // Print prints the content of the File to the standard output (console) 370 // and returns the File unchanged. 371 func (f *File) Print() *File { fmt.Println(f); return f } 372 373 // Read opens the named file with a read-lock and returns its contents. 374 func (f *File) Read() Result[String] { 375 if r := f.Open(); r.IsErr() { 376 return Err[String](r.Err()) 377 } 378 379 defer f.Close() 380 381 content, err := io.ReadAll(f.file) 382 if err != nil { 383 return Err[String](err) 384 } 385 386 return Ok(String(content)) 387 } 388 389 // Remove removes the file. 390 func (f *File) Remove() Result[*File] { 391 if err := os.Remove(f.name.Std()); err != nil { 392 return Err[*File](err) 393 } 394 395 return Ok(f) 396 } 397 398 // Rename renames the file to the specified new path. 399 func (f *File) Rename(newpath String) Result[*File] { 400 if !f.Exist() { 401 return Err[*File](&ErrFileNotExist{f.name.Std()}) 402 } 403 404 nf := NewFile(newpath).createAll() 405 406 if err := os.Rename(f.name.Std(), newpath.Std()); err != nil { 407 return Err[*File](err) 408 } 409 410 return nf 411 } 412 413 // Split splits the file path into its directory and file components. 414 func (f *File) Split() (*Dir, *File) { 415 path := f.Path() 416 if path.IsErr() { 417 return nil, nil 418 } 419 420 dir, file := filepath.Split(path.Ok().Std()) 421 422 return NewDir(String(dir)), NewFile(String(file)) 423 } 424 425 // Stat returns the fs.FileInfo of the file. 426 // It calls the file's Stat method if the file is open, or os.Stat otherwise. 427 func (f *File) Stat() Result[fs.FileInfo] { 428 if f.file != nil { 429 return ResultOf(f.file.Stat()) 430 } 431 432 return ResultOf(os.Stat(f.name.Std())) 433 } 434 435 // Lstat retrieves information about the symbolic link represented by the *File instance. 436 // It returns a Result[fs.FileInfo] containing details about the symbolic link's metadata. 437 // Unlike Stat, Lstat does not follow the link and provides information about the link itself. 438 func (f *File) Lstat() Result[fs.FileInfo] { 439 return ResultOf(os.Lstat(f.name.Std())) 440 } 441 442 // IsDir checks if the file is a directory. 443 func (f *File) IsDir() bool { 444 stat := f.Stat() 445 return stat.IsOk() && stat.Ok().IsDir() 446 } 447 448 // IsLink checks if the file is a symbolic link. 449 func (f *File) IsLink() bool { 450 stat := f.Lstat() 451 return stat.IsOk() && stat.Ok().Mode()&os.ModeSymlink != 0 452 } 453 454 // Std returns the underlying *os.File instance. 455 // Don't forget to close the file with g.File().Close()! 456 func (f *File) Std() *os.File { return f.file } 457 458 // CreateTemp creates a new temporary file in the specified directory with the 459 // specified name pattern and returns a Result, which contains a pointer to the File 460 // or an error if the operation fails. 461 // If no directory is specified, the default directory for temporary files is used. 462 // If no name pattern is specified, the default pattern "*" is used. 463 // 464 // Parameters: 465 // 466 // - args ...String: A variadic parameter specifying the directory and/or name 467 // pattern for the temporary file. 468 // 469 // Returns: 470 // 471 // - *File: A pointer to the File representing the temporary file. 472 // 473 // Example usage: 474 // 475 // f := g.NewFile("") 476 // tmpfile := f.CreateTemp() // Creates a temporary file with default settings 477 // tmpfileWithDir := f.CreateTemp("mydir") // Creates a temporary file in "mydir" directory 478 // tmpfileWithPattern := f.CreateTemp("", "tmp") // Creates a temporary file with "tmp" pattern 479 func (*File) CreateTemp(args ...String) Result[*File] { 480 dir := "" 481 pattern := "*" 482 483 if len(args) != 0 { 484 if len(args) > 1 { 485 pattern = args[1].Std() 486 } 487 488 dir = args[0].Std() 489 } 490 491 tmpfile, err := os.CreateTemp(dir, pattern) 492 if err != nil { 493 return Err[*File](err) 494 } 495 496 ntmpfile := NewFile(String(tmpfile.Name())) 497 ntmpfile.file = tmpfile 498 499 defer ntmpfile.Close() 500 501 return Ok(ntmpfile) 502 } 503 504 // Write opens the named file (creating it with the given permissions if needed), 505 // then write-locks it and overwrites it with the given content. 506 func (f *File) Write(content String, mode ...os.FileMode) Result[*File] { 507 return f.WriteFromReader(content.Reader(), mode...) 508 } 509 510 // WriteFromReader takes an io.Reader (scr) as input and writes the data from the reader into the file. 511 // If no FileMode is provided, the default FileMode (0644) is used. 512 func (f *File) WriteFromReader(scr io.Reader, mode ...os.FileMode) Result[*File] { 513 if f.file == nil { 514 if r := f.createAll(); r.IsErr() { 515 return r 516 } 517 } 518 519 fmode := FileDefault 520 if len(mode) != 0 { 521 fmode = mode[0] 522 } 523 524 filePath := f.filePath() 525 if filePath.IsErr() { 526 return Err[*File](filePath.Err()) 527 } 528 529 f = NewFile(filePath.Ok()) 530 531 if r := f.OpenFile(os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fmode); r.IsErr() { 532 return Err[*File](r.Err()) 533 } 534 535 defer f.Close() 536 537 _, err := io.Copy(f.file, scr) 538 if err != nil { 539 return Err[*File](err) 540 } 541 542 err = f.file.Sync() 543 if err != nil { 544 return Err[*File](err) 545 } 546 547 return Ok(f) 548 } 549 550 // dirPath returns the absolute path of the directory containing the file. 551 func (f *File) dirPath() Result[String] { 552 var ( 553 path string 554 err error 555 ) 556 557 if f.IsDir() { 558 path, err = filepath.Abs(f.name.Std()) 559 } else { 560 path, err = filepath.Abs(filepath.Dir(f.name.Std())) 561 } 562 563 if err != nil { 564 return Err[String](err) 565 } 566 567 return Ok(String(path)) 568 } 569 570 // filePath returns the full file path, including the directory and file name. 571 func (f *File) filePath() Result[String] { 572 dirPath := f.dirPath() 573 if dirPath.IsErr() { 574 return Err[String](dirPath.Err()) 575 } 576 577 if f.IsDir() { 578 return dirPath 579 } 580 581 return Ok(String(filepath.Join(dirPath.Ok().Std(), filepath.Base(f.name.Std())))) 582 } 583 584 func (f *File) createAll() Result[*File] { 585 dirPath := f.dirPath() 586 if dirPath.IsErr() { 587 return Err[*File](dirPath.Err()) 588 } 589 590 if !f.Exist() { 591 if err := os.MkdirAll(dirPath.Ok().Std(), DirDefault); err != nil { 592 return Err[*File](err) 593 } 594 } 595 596 return Ok(f) 597 }