go-hep.org/x/hep@v0.38.1/groot/riofs/key.go (about)

     1  // Copyright ©2017 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 riofs
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"reflect"
    11  	"strings"
    12  	"time"
    13  
    14  	"go-hep.org/x/hep/groot/internal/rcompress"
    15  	"go-hep.org/x/hep/groot/rbytes"
    16  	"go-hep.org/x/hep/groot/root"
    17  	"go-hep.org/x/hep/groot/rtypes"
    18  	"go-hep.org/x/hep/groot/rvers"
    19  )
    20  
    21  // KeyOption configures how a Key may be created.
    22  type KeyOption func(cfg *keyCfg) error
    23  
    24  // WithKeyCompression configures a Key to use the provided compression scheme.
    25  func WithKeyCompression(compression int32) KeyOption {
    26  	return func(cfg *keyCfg) error {
    27  		cfg.compr = rcompress.SettingsFrom(compression)
    28  		return nil
    29  	}
    30  }
    31  
    32  type keyCfg struct {
    33  	compr rcompress.Settings
    34  }
    35  
    36  func newKeyCfg() *keyCfg {
    37  	return &keyCfg{
    38  		compr: rcompress.Settings{Alg: rcompress.Inherit},
    39  	}
    40  }
    41  
    42  // noKeyError is the error returned when a riofs.Key could not be found.
    43  type noKeyError struct {
    44  	key string
    45  	obj root.Named
    46  }
    47  
    48  func (err noKeyError) Error() string {
    49  	return fmt.Sprintf("riofs: %s: could not find key %q", err.obj.Name(), err.key)
    50  }
    51  
    52  // keyTypeError is the error returned when a riofs.Key was found but the associated
    53  // value is not of the expected type.
    54  type keyTypeError struct {
    55  	key   string
    56  	class string
    57  }
    58  
    59  func (err keyTypeError) Error() string {
    60  	return fmt.Sprintf("riofs: inconsistent value type for key %q (type=%s)", err.key, err.class)
    61  }
    62  
    63  // Key is a key (a label) in a ROOT file
    64  //
    65  //	The Key class includes functions to book space on a file,
    66  //	 to create I/O buffers, to fill these buffers
    67  //	 to compress/uncompress data buffers.
    68  //
    69  //	Before saving (making persistent) an object on a file, a key must
    70  //	be created. The key structure contains all the information to
    71  //	uniquely identify a persistent object on a file.
    72  //	The Key class is used by ROOT:
    73  //	  - to write an object in the Current Directory
    74  //	  - to write a new ntuple buffer
    75  type Key struct {
    76  	f *File // underlying file
    77  
    78  	rvers    int16     // version of the Key struct
    79  	nbytes   int32     // number of bytes for the compressed object+key
    80  	objlen   int32     // length of uncompressed object
    81  	datetime time.Time // Date/Time when the object was written
    82  	keylen   int32     // number of bytes for the Key struct
    83  	cycle    int16     // cycle number of the object
    84  
    85  	// address of the object on file (points to Key.bytes)
    86  	// this is a redundant information used to cross-check
    87  	// the data base integrity
    88  	seekkey  int64
    89  	seekpdir int64 // pointer to the directory supporting this object
    90  
    91  	class string // object class name
    92  	name  string // name of the object
    93  	title string // title of the object
    94  
    95  	left int32 // number of bytes left in current segment.
    96  
    97  	buf []byte      // buffer of the Key's value
    98  	obj root.Object // Key's value
    99  
   100  	otyp reflect.Type // Go type of the Key's payload.
   101  
   102  	parent Directory // directory holding this key
   103  }
   104  
   105  func newKey(dir *tdirectoryFile, name, title, class string, objlen int32, f *File) Key {
   106  	k := Key{
   107  		f:        f,
   108  		rvers:    rvers.Key,
   109  		objlen:   objlen,
   110  		datetime: nowUTC(),
   111  		cycle:    1,
   112  		class:    class,
   113  		name:     name,
   114  		title:    title,
   115  		seekpdir: f.begin, // FIXME(sbinet): see https://sft.its.cern.ch/jira/browse/ROOT-10352
   116  		parent:   dir,
   117  	}
   118  	k.keylen = k.sizeof()
   119  	// FIXME(sbinet): this assumes the key-payload isn't compressed.
   120  	// if the key's payload is actually compressed, we introduce a hole
   121  	// with the f.setEnd call below.
   122  	k.nbytes = k.objlen + k.keylen
   123  	eof := f.end
   124  	if objlen > 0 {
   125  		k.seekkey = eof
   126  		err := f.setEnd(k.seekkey + int64(k.nbytes))
   127  		if err != nil {
   128  			panic(err)
   129  		}
   130  	}
   131  
   132  	if eof > kStartBigFile {
   133  		k.rvers += 1000
   134  	}
   135  
   136  	if dir != nil {
   137  		k.seekpdir = dir.seekdir
   138  	}
   139  
   140  	return k
   141  }
   142  
   143  // NewKey creates a new key from the provided serialized object buffer.
   144  // NewKey puts the key and its payload at the end of the provided file f.
   145  // Depending on the file configuration, NewKey may compress the provided object buffer.
   146  func NewKey(dir Directory, name, title, class string, cycle int16, obj []byte, f *File, kopts ...KeyOption) (Key, error) {
   147  	var d *tdirectoryFile
   148  	if dir != nil {
   149  		d = dir.(*tdirectoryFile)
   150  	}
   151  	return newKeyFromBuf(d, name, title, class, cycle, obj, f, kopts)
   152  }
   153  
   154  func newKeyFrom(dir *tdirectoryFile, name, title, class string, obj root.Object, f *File, kopts []KeyOption) (Key, error) {
   155  	var err error
   156  	if dir == nil {
   157  		dir = &f.dir
   158  	}
   159  
   160  	kcfg := newKeyCfg()
   161  	for _, opt := range kopts {
   162  		err = opt(kcfg)
   163  		if err != nil {
   164  			return Key{}, fmt.Errorf("riofs: could not setup Key option: %w", err)
   165  		}
   166  	}
   167  
   168  	keylen := keylenFor(name, title, class, dir, f.end)
   169  
   170  	buf := rbytes.NewWBuffer(nil, nil, uint32(keylen), dir.file)
   171  	switch obj := obj.(type) {
   172  	case rbytes.Marshaler:
   173  		_, err = obj.MarshalROOT(buf)
   174  		if err != nil {
   175  			return Key{}, fmt.Errorf("riofs: could not marshal object %T for key=%q: %w", obj, name, err)
   176  		}
   177  	default:
   178  		return Key{}, fmt.Errorf("riofs: object %T can not be ROOT serialized", obj)
   179  	}
   180  
   181  	objlen := int32(len(buf.Bytes()))
   182  	k := Key{
   183  		f:        f,
   184  		nbytes:   keylen + objlen,
   185  		rvers:    rvers.Key,
   186  		keylen:   keylen,
   187  		objlen:   objlen,
   188  		datetime: nowUTC(),
   189  		cycle:    1,
   190  		class:    class,
   191  		name:     name,
   192  		title:    title,
   193  		seekkey:  f.end,
   194  		seekpdir: dir.seekdir,
   195  		obj:      obj,
   196  		otyp:     reflect.TypeOf(obj),
   197  		parent:   dir,
   198  	}
   199  	if f.IsBigFile() {
   200  		k.rvers += 1000
   201  	}
   202  
   203  	compress := k.f.compression
   204  	if kcfg.compr.Alg != rcompress.Inherit {
   205  		compress = kcfg.compr.Compression()
   206  	}
   207  
   208  	k.buf, err = rcompress.Compress(nil, buf.Bytes(), compress)
   209  	if err != nil {
   210  		return k, fmt.Errorf("riofs: could not compress object %T for key %q: %w", obj, name, err)
   211  	}
   212  	k.nbytes = k.keylen + int32(len(k.buf))
   213  
   214  	err = f.setEnd(k.seekkey + int64(k.nbytes))
   215  	if err != nil {
   216  		return k, fmt.Errorf("riofs: could not update ROOT file end: %w", err)
   217  	}
   218  
   219  	return k, nil
   220  }
   221  
   222  func newKeyFromBuf(dir *tdirectoryFile, name, title, class string, cycle int16, buf []byte, f *File, kopts []KeyOption) (Key, error) {
   223  	var err error
   224  	if dir == nil {
   225  		dir = &f.dir
   226  	}
   227  
   228  	kcfg := newKeyCfg()
   229  	for _, opt := range kopts {
   230  		err = opt(kcfg)
   231  		if err != nil {
   232  			return Key{}, fmt.Errorf("riofs: could not setup Key option: %w", err)
   233  		}
   234  	}
   235  
   236  	keylen := keylenFor(name, title, class, dir, f.end)
   237  	objlen := int32(len(buf))
   238  	k := Key{
   239  		f:        f,
   240  		nbytes:   keylen + objlen,
   241  		rvers:    rvers.Key,
   242  		keylen:   keylen,
   243  		objlen:   objlen,
   244  		datetime: nowUTC(),
   245  		cycle:    cycle,
   246  		class:    class,
   247  		name:     name,
   248  		title:    title,
   249  		seekkey:  f.end,
   250  		seekpdir: dir.seekdir,
   251  		parent:   dir,
   252  	}
   253  	if f.IsBigFile() {
   254  		k.rvers += 1000
   255  	}
   256  
   257  	compress := k.f.compression
   258  	if kcfg.compr.Alg != rcompress.Inherit {
   259  		compress = kcfg.compr.Compression()
   260  	}
   261  
   262  	k.buf, err = rcompress.Compress(nil, buf, compress)
   263  	if err != nil {
   264  		return k, fmt.Errorf("riofs: could not compress object %s for key %q: %w", class, name, err)
   265  	}
   266  	k.nbytes = k.keylen + int32(len(k.buf))
   267  
   268  	err = f.setEnd(k.seekkey + int64(k.nbytes))
   269  	if err != nil {
   270  		return k, fmt.Errorf("riofs: could not update ROOT file end: %w", err)
   271  	}
   272  
   273  	return k, nil
   274  }
   275  
   276  // NewKeyForBasketInternal creates a new empty key.
   277  // This is needed for Tree/Branch/Basket persistency.
   278  //
   279  // DO NOT USE.
   280  func NewKeyForBasketInternal(dir Directory, name, title, class string, cycle int16) Key {
   281  	var (
   282  		f = fileOf(dir)
   283  		d *tdirectoryFile
   284  	)
   285  	switch v := dir.(type) {
   286  	case *File:
   287  		d = &v.dir
   288  	case *tdirectoryFile:
   289  		d = v
   290  	default:
   291  		panic(fmt.Errorf("riofs: invalid directory type %T", dir))
   292  	}
   293  
   294  	k := Key{
   295  		f:        f,
   296  		rvers:    rvers.Key,
   297  		cycle:    cycle,
   298  		datetime: nowUTC(),
   299  		class:    class,
   300  		name:     name,
   301  		title:    title,
   302  		seekpdir: f.begin, // FIXME(sbinet): see https://sft.its.cern.ch/jira/browse/ROOT-10352
   303  		parent:   dir,
   304  	}
   305  	k.keylen = k.sizeof()
   306  	k.nbytes = k.keylen
   307  	if f.IsBigFile() {
   308  		k.rvers += 1000
   309  	}
   310  
   311  	if d != nil {
   312  		k.seekpdir = d.seekdir
   313  	}
   314  
   315  	return k
   316  }
   317  
   318  // KeyFromDir creates a new empty key (with no associated payload object)
   319  // with provided name and title, and the expected object type name.
   320  // The key will be held by the provided directory.
   321  func KeyFromDir(dir Directory, name, title, class string) Key {
   322  	f := fileOf(dir)
   323  	var k Key
   324  	switch v := dir.(type) {
   325  	case *File:
   326  		k = newKey(&v.dir, name, title, class, 0, f)
   327  	case *tdirectoryFile:
   328  		k = newKey(v, name, title, class, 0, f)
   329  	default:
   330  		panic(fmt.Errorf("riofs: invalid directory type %T", dir))
   331  	}
   332  	return k
   333  }
   334  
   335  func (k *Key) RVersion() int16 { return k.rvers }
   336  
   337  func (*Key) Class() string {
   338  	return "TKey"
   339  }
   340  
   341  func (k *Key) ClassName() string {
   342  	return k.class
   343  }
   344  
   345  func (k *Key) Name() string {
   346  	return k.name
   347  }
   348  
   349  func (k *Key) Title() string {
   350  	return k.title
   351  }
   352  
   353  func (k *Key) Cycle() int {
   354  	return int(k.cycle)
   355  }
   356  
   357  func (k *Key) Nbytes() int32  { return k.nbytes }
   358  func (k *Key) ObjLen() int32  { return k.objlen }
   359  func (k *Key) KeyLen() int32  { return k.keylen }
   360  func (k *Key) SeekKey() int64 { return k.seekkey }
   361  func (k *Key) Buffer() []byte { return k.buf }
   362  
   363  func (k *Key) SetFile(f *File)      { k.f = f }
   364  func (k *Key) SetBuffer(buf []byte) { k.buf = buf; k.objlen = int32(len(buf)) }
   365  
   366  // ObjectType returns the Key's payload type.
   367  //
   368  // ObjectType returns nil if the Key's payload type is not known
   369  // to the registry of groot.
   370  func (k *Key) ObjectType() reflect.Type {
   371  	if k.otyp != nil {
   372  		return k.otyp
   373  	}
   374  	if !rtypes.Factory.HasKey(k.class) {
   375  		return nil
   376  	}
   377  	k.otyp = rtypes.Factory.Get(k.class)().Type()
   378  	return k.otyp
   379  }
   380  
   381  // Value returns the data corresponding to the Key's value
   382  func (k *Key) Value() any {
   383  	v, err := k.Object()
   384  	if err != nil {
   385  		panic(fmt.Errorf("error loading payload for %q: %w", k.Name(), err))
   386  	}
   387  	return v
   388  }
   389  
   390  // Object returns the (ROOT) object corresponding to the Key's value.
   391  func (k *Key) Object() (root.Object, error) {
   392  	if k.obj != nil {
   393  		return k.obj, nil
   394  	}
   395  
   396  	buf, err := k.Bytes()
   397  	if err != nil {
   398  		return nil, fmt.Errorf("riofs: could not load key payload: %w", err)
   399  	}
   400  
   401  	fct := rtypes.Factory.Get(k.class)
   402  	if fct == nil {
   403  		return nil, fmt.Errorf("riofs: no registered factory for class %q (key=%q)", k.class, k.Name())
   404  	}
   405  
   406  	v := fct()
   407  	obj, ok := v.Interface().(root.Object)
   408  	if !ok {
   409  		return nil, fmt.Errorf("riofs: class %q does not implement root.Object (key=%q)", k.class, k.Name())
   410  	}
   411  
   412  	vv, ok := obj.(rbytes.Unmarshaler)
   413  	if !ok {
   414  		return nil, fmt.Errorf("riofs: class %q does not implement rbytes.Unmarshaler (key=%q)", k.class, k.Name())
   415  	}
   416  
   417  	// use autogen context to automatically generate missing streamer infos when reading "old" files.
   418  	sictx := autogenCtx{k.f}
   419  	err = vv.UnmarshalROOT(rbytes.NewRBuffer(buf, nil, uint32(k.keylen), sictx))
   420  	if err != nil {
   421  		return nil, fmt.Errorf("riofs: could not unmarshal key payload: %w", err)
   422  	}
   423  
   424  	if vv, ok := obj.(SetFiler); ok {
   425  		vv.SetFile(k.f)
   426  	}
   427  	if dir, ok := obj.(*tdirectoryFile); ok {
   428  		dir.file = k.f
   429  		dir.dir.parent = k.parent
   430  		dir.dir.named.SetName(k.Name())
   431  		dir.dir.named.SetTitle(k.Name())
   432  		dir.classname = k.class
   433  		err = dir.readKeys()
   434  		if err != nil {
   435  			return nil, err
   436  		}
   437  	}
   438  
   439  	k.obj = obj
   440  	return obj, nil
   441  }
   442  
   443  // Bytes returns the buffer of bytes corresponding to the Key's value
   444  func (k *Key) Bytes() ([]byte, error) {
   445  	data, err := k.load(nil)
   446  	if err != nil {
   447  		return nil, err
   448  	}
   449  	return data, nil
   450  }
   451  
   452  func (k *Key) Load(buf []byte) ([]byte, error) {
   453  	return k.load(buf)
   454  }
   455  
   456  func (k *Key) load(buf []byte) ([]byte, error) {
   457  	buf = rbytes.ResizeU8(buf, int(k.objlen))
   458  	if len(k.buf) > 0 {
   459  		copy(buf, k.buf)
   460  		return buf, nil
   461  	}
   462  	if k.isCompressed() {
   463  		start := k.seekkey + int64(k.keylen)
   464  		sr := io.NewSectionReader(k.f, start, int64(k.nbytes)-int64(k.keylen))
   465  		err := rcompress.Decompress(buf, sr)
   466  		if err != nil {
   467  			return nil, fmt.Errorf("riofs: could not decompress key payload: %w", err)
   468  		}
   469  		return buf, nil
   470  	}
   471  	start := k.seekkey + int64(k.keylen)
   472  	r := io.NewSectionReader(k.f, start, int64(k.nbytes))
   473  	_, err := io.ReadFull(r, buf)
   474  	if err != nil {
   475  		return nil, fmt.Errorf("riofs: could not read key payload: %w", err)
   476  	}
   477  	return buf, nil
   478  }
   479  
   480  // func (k *Key) store() error {
   481  // 	if k.buf != nil {
   482  // 		return nil
   483  // 	}
   484  //
   485  // 	k.keylen = k.sizeof()
   486  //
   487  // 	buf := rbytes.NewWBuffer(make([]byte, k.objlen), nil, uint32(k.keylen), k.f)
   488  // 	_, err := k.obj.(rbytes.Marshaler).MarshalROOT(buf)
   489  // 	if err != nil {
   490  // 		return err
   491  // 	}
   492  // 	k.objlen = int32(len(buf.Bytes()))
   493  // 	k.buf, err = rcompress.Compress(nil, buf.Bytes(), k.f.compression)
   494  // 	if err != nil {
   495  // 		return err
   496  // 	}
   497  // 	nbytes := int32(len(k.buf))
   498  // 	k.nbytes = k.keylen + nbytes
   499  //
   500  // 	if k.seekkey <= 0 {
   501  // 		panic("impossible: seekkey <= 0")
   502  // 	}
   503  //
   504  // 	return nil
   505  // }
   506  
   507  func (k *Key) isCompressed() bool {
   508  	return k.objlen != k.nbytes-k.keylen
   509  }
   510  
   511  func (k *Key) isBigFile() bool {
   512  	return k.rvers > 1000
   513  }
   514  
   515  // sizeof returns the size in bytes of the key header structure.
   516  func (k *Key) sizeof() int32 {
   517  	return keylenFor(k.name, k.title, k.class, &k.f.dir, k.f.end)
   518  }
   519  
   520  func keylenFor(name, title, class string, dir *tdirectoryFile, eof int64) int32 {
   521  	nbytes := int32(22)
   522  	if dir.isBigFile() || eof > kStartBigFile {
   523  		nbytes += 8
   524  	}
   525  	nbytes += datimeSizeof()
   526  	nbytes += tstringSizeof(class)
   527  	nbytes += tstringSizeof(name)
   528  	nbytes += tstringSizeof(title)
   529  	if class == "TBasket" {
   530  		nbytes += 2 // version
   531  		nbytes += 4 // bufsize
   532  		nbytes += 4 // nevsize
   533  		nbytes += 4 // nevbuf
   534  		nbytes += 4 // last
   535  		nbytes += 1 // flag
   536  	}
   537  	return nbytes
   538  }
   539  
   540  // MarshalROOT encodes the key to the provided buffer.
   541  func (k *Key) MarshalROOT(w *rbytes.WBuffer) (int, error) {
   542  	if w.Err() != nil {
   543  		return 0, w.Err()
   544  	}
   545  
   546  	pos := w.Pos()
   547  
   548  	w.WriteI32(k.nbytes)
   549  	if k.nbytes < 0 {
   550  		return int(w.Pos() - pos), nil
   551  	}
   552  
   553  	if k.seekkey > kStartBigFile {
   554  		if k.rvers < 1000 {
   555  			k.rvers += 1000
   556  		}
   557  	}
   558  
   559  	w.WriteI16(k.RVersion())
   560  	w.WriteI32(k.objlen)
   561  	w.WriteU32(time2datime(k.datetime))
   562  	w.WriteI16(int16(k.keylen))
   563  	w.WriteI16(k.cycle)
   564  	switch {
   565  	case k.isBigFile():
   566  		w.WriteI64(k.seekkey)
   567  		// FIXME(sbinet): handle PidOffsetShift and PidOffset that are stored in the 16 highest bits of seekpdir
   568  		w.WriteI64(k.seekpdir)
   569  	default:
   570  		w.WriteI32(int32(k.seekkey))
   571  		w.WriteI32(int32(k.seekpdir))
   572  	}
   573  	w.WriteString(k.class)
   574  	w.WriteString(k.name)
   575  	w.WriteString(k.title)
   576  
   577  	return int(w.Pos() - pos), nil
   578  }
   579  
   580  // UnmarshalROOT decodes the content of data into the Key
   581  func (k *Key) UnmarshalROOT(r *rbytes.RBuffer) error {
   582  	if r.Err() != nil {
   583  		return r.Err()
   584  	}
   585  
   586  	k.nbytes = r.ReadI32()
   587  	if k.nbytes < 0 {
   588  		k.class = "[GAP]"
   589  		return nil
   590  	}
   591  
   592  	k.rvers = r.ReadI16()
   593  	k.objlen = r.ReadI32()
   594  	k.datetime = datime2time(r.ReadU32())
   595  	k.keylen = int32(r.ReadI16())
   596  	k.cycle = r.ReadI16()
   597  
   598  	switch {
   599  	case k.isBigFile():
   600  		k.seekkey = r.ReadI64()
   601  		// FIXME(sbinet): handle PidOffsetShift and PidOffset that are stored in the 16 highest bits of seekpdir
   602  		k.seekpdir = r.ReadI64()
   603  	default:
   604  		k.seekkey = int64(r.ReadI32())
   605  		k.seekpdir = int64(r.ReadI32())
   606  	}
   607  
   608  	k.class = r.ReadString()
   609  	k.name = r.ReadString()
   610  	k.title = r.ReadString()
   611  
   612  	return r.Err()
   613  }
   614  
   615  // writeFile writes the key's payload to the file
   616  func (k *Key) writeFile(f *File) (int, error) {
   617  	if k.left > 0 {
   618  		w := rbytes.NewWBuffer(nil, nil, 0, nil)
   619  		w.WriteI32(int32(-k.left))
   620  		k.buf = append(k.buf, w.Bytes()...)
   621  	}
   622  
   623  	buf := rbytes.NewWBuffer(make([]byte, k.nbytes), nil, 0, f)
   624  	_, err := k.MarshalROOT(buf)
   625  	if err != nil {
   626  		return 0, err
   627  	}
   628  
   629  	n, err := f.w.WriteAt(buf.Bytes(), k.seekkey)
   630  	if err != nil {
   631  		return n, err
   632  	}
   633  	nn, err := f.w.WriteAt(k.buf, k.seekkey+int64(k.keylen))
   634  	n += nn
   635  	if err != nil {
   636  		return n, err
   637  	}
   638  
   639  	k.buf = nil
   640  	return n, nil
   641  }
   642  
   643  func (k *Key) records(w io.Writer, indent int) error {
   644  	hdr := strings.Repeat("  ", indent)
   645  	fmt.Fprintf(w, "%s=== key %q ===\n", hdr, k.Name())
   646  	fmt.Fprintf(w, "%snbytes:    %d\n", hdr, k.nbytes)
   647  	fmt.Fprintf(w, "%skeylen:    %d\n", hdr, k.keylen)
   648  	fmt.Fprintf(w, "%sobjlen:    %d\n", hdr, k.objlen)
   649  	fmt.Fprintf(w, "%scycle:     %d\n", hdr, k.cycle)
   650  	fmt.Fprintf(w, "%sseek-key:  %d\n", hdr, k.seekkey)
   651  	fmt.Fprintf(w, "%sseek-pdir: %d\n", hdr, k.seekpdir)
   652  	fmt.Fprintf(w, "%sclass:     %q\n", hdr, k.class)
   653  	parent := "<nil>"
   654  	if k.parent != nil {
   655  		parent = fmt.Sprintf("@%d", k.parent.(*tdirectoryFile).seekdir)
   656  	}
   657  	fmt.Fprintf(w, "%sparent:    %s\n", hdr, parent)
   658  
   659  	switch k.class {
   660  	case "TDirectory", "TDirectoryFile":
   661  		obj, err := k.Object()
   662  		if err != nil {
   663  			return fmt.Errorf("could not load object of key %q: %w", k.Name(), err)
   664  		}
   665  		return obj.(*tdirectoryFile).records(w, indent+1)
   666  	}
   667  	return nil
   668  }
   669  
   670  func init() {
   671  	f := func() reflect.Value {
   672  		o := &Key{}
   673  		return reflect.ValueOf(o)
   674  	}
   675  	rtypes.Factory.Add("TKey", f)
   676  }
   677  
   678  var (
   679  	_ root.Object        = (*Key)(nil)
   680  	_ root.Named         = (*Key)(nil)
   681  	_ rbytes.Marshaler   = (*Key)(nil)
   682  	_ rbytes.Unmarshaler = (*Key)(nil)
   683  )