github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmn/jsp/file.go (about)

     1  // Package jsp (JSON persistence) provides utilities to store and load arbitrary
     2  // JSON-encoded structures with optional checksumming and compression.
     3  /*
     4   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     5   */
     6  package jsp
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	"strings"
    13  
    14  	"github.com/NVIDIA/aistore/cmn/cos"
    15  	"github.com/NVIDIA/aistore/cmn/debug"
    16  	"github.com/NVIDIA/aistore/cmn/nlog"
    17  )
    18  
    19  const (
    20  	signature = "aistore" // file signature
    21  	//                              0 ---------------- 63  64 ------ 95 | 96 ------ 127
    22  	prefLen = 2 * cos.SizeofI64 // [ signature | jsp ver | meta version |   bit flags  ]
    23  )
    24  
    25  // current JSP version
    26  const (
    27  	Metaver = 3
    28  )
    29  
    30  //////////////////
    31  // main methods //
    32  //////////////////
    33  
    34  func SaveMeta(filepath string, meta Opts, wto cos.WriterTo2) error {
    35  	return Save(filepath, meta, meta.JspOpts(), wto)
    36  }
    37  
    38  func Save(filepath string, v any, opts Options, wto cos.WriterTo2) (err error) {
    39  	var (
    40  		file *os.File
    41  		tmp  = filepath + ".tmp." + cos.GenTie()
    42  	)
    43  	if file, err = cos.CreateFile(tmp); err != nil {
    44  		return
    45  	}
    46  	defer func() {
    47  		if err == nil {
    48  			return
    49  		}
    50  		if nestedErr := cos.RemoveFile(tmp); nestedErr != nil {
    51  			nlog.Errorf("Nested (%v): failed to remove %s, err: %v", err, tmp, nestedErr)
    52  		}
    53  	}()
    54  	if wto != nil {
    55  		err = wto.WriteTo2(file)
    56  	} else {
    57  		debug.Assert(v != nil)
    58  		err = Encode(file, v, opts)
    59  	}
    60  	if err != nil {
    61  		nlog.Errorf("Failed to encode %s: %v", filepath, err)
    62  		cos.Close(file)
    63  		return
    64  	}
    65  	if err = cos.FlushClose(file); err != nil {
    66  		nlog.Errorf("Failed to flush and close %s: %v", tmp, err)
    67  		return
    68  	}
    69  	err = os.Rename(tmp, filepath)
    70  	return
    71  }
    72  
    73  func LoadMeta(filepath string, meta Opts) (*cos.Cksum, error) {
    74  	return Load(filepath, meta, meta.JspOpts())
    75  }
    76  
    77  func _tag(filepath string, v any) (tag string) {
    78  	tag = fmt.Sprintf("%T", v)
    79  	if i := strings.LastIndex(tag, "."); i > 0 {
    80  		tag = tag[i+1:]
    81  	}
    82  	return tag + " at " + filepath
    83  }
    84  
    85  func Load(filepath string, v any, opts Options) (checksum *cos.Cksum, err error) {
    86  	var file *os.File
    87  	file, err = os.Open(filepath)
    88  	if err != nil {
    89  		return
    90  	}
    91  	checksum, err = Decode(file, v, opts, _tag(filepath, v))
    92  	if err == nil {
    93  		return
    94  	}
    95  	if errors.Is(err, &cos.ErrBadCksum{}) {
    96  		if errRm := os.Remove(filepath); errRm == nil {
    97  			cos.Errorf("jsp: %v, removed %q", err, filepath)
    98  		} else {
    99  			cos.Errorf("jsp: %v, failed to remove %q: %v", err, filepath, errRm)
   100  		}
   101  	}
   102  	return
   103  }