github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/istructsmem/internal/containers/impl.go (about)

     1  /*
     2   * Copyright (c) 2021-present Sigma-Soft, Ltd.
     3   * @author: Nikolay Nikitin
     4   */
     5  
     6  package containers
     7  
     8  import (
     9  	"context"
    10  	"encoding/binary"
    11  	"errors"
    12  	"fmt"
    13  
    14  	"github.com/voedger/voedger/pkg/appdef"
    15  	"github.com/voedger/voedger/pkg/istorage"
    16  	"github.com/voedger/voedger/pkg/istructsmem/internal/consts"
    17  	"github.com/voedger/voedger/pkg/istructsmem/internal/utils"
    18  	"github.com/voedger/voedger/pkg/istructsmem/internal/vers"
    19  )
    20  
    21  func newContainers() *Containers {
    22  	return &Containers{
    23  		containers: make(map[string]ContainerID),
    24  		ids:        make(map[ContainerID]string),
    25  		lastID:     ContainerNameIDSysLast,
    26  	}
    27  }
    28  
    29  // Retrieve container for specified ID
    30  func (cnt *Containers) Container(id ContainerID) (name string, err error) {
    31  	name, ok := cnt.ids[id]
    32  	if ok {
    33  		return name, nil
    34  	}
    35  
    36  	return "", fmt.Errorf("unknown container ID «%v»: %w", id, ErrContainerIDNotFound)
    37  }
    38  
    39  // Retrieve ID for specified container
    40  func (cnt *Containers) ID(name string) (ContainerID, error) {
    41  	if id, ok := cnt.containers[name]; ok {
    42  		return id, nil
    43  	}
    44  	return 0, fmt.Errorf("unknown container name «%v»: %w", name, ErrContainerNotFound)
    45  }
    46  
    47  // Loads all container from storage, add all known system and application containers and store if some changes. Must be called at application starts
    48  func (cnt *Containers) Prepare(storage istorage.IAppStorage, versions *vers.Versions, appDef appdef.IAppDef) (err error) {
    49  	if err = cnt.load(storage, versions); err != nil {
    50  		return err
    51  	}
    52  
    53  	if err = cnt.collectAll(appDef); err != nil {
    54  		return err
    55  	}
    56  
    57  	if cnt.changes > 0 {
    58  		if err := cnt.store(storage, versions); err != nil {
    59  			return err
    60  		}
    61  	}
    62  
    63  	return nil
    64  }
    65  
    66  // Retrieves and stores IDs for all known containers in application types. Must be called then application starts
    67  func (cnt *Containers) collectAll(appDef appdef.IAppDef) (err error) {
    68  
    69  	// system containers
    70  	cnt.collectSys("", NullContainerID)
    71  
    72  	// application containers
    73  	if appDef != nil {
    74  		appDef.Types(
    75  			func(t appdef.IType) {
    76  				if cont, ok := t.(appdef.IContainers); ok {
    77  					for _, c := range cont.Containers() {
    78  						err = errors.Join(err, cnt.collect(c.Name()))
    79  					}
    80  				}
    81  			})
    82  	}
    83  
    84  	return err
    85  }
    86  
    87  // Retrieves and stores ID for specified application container
    88  func (cnt *Containers) collect(name string) (err error) {
    89  	if _, ok := cnt.containers[name]; ok {
    90  		return nil // already known container
    91  	}
    92  
    93  	for id := cnt.lastID + 1; id < MaxAvailableContainerID; id++ {
    94  		if _, ok := cnt.ids[id]; !ok {
    95  			cnt.containers[name] = id
    96  			cnt.ids[id] = name
    97  			cnt.lastID = id
    98  			cnt.changes++
    99  			return nil
   100  		}
   101  	}
   102  
   103  	return ErrContainerIDsExceeds
   104  }
   105  
   106  // Remember ID for specified system container
   107  func (cnt *Containers) collectSys(name string, id ContainerID) {
   108  	cnt.containers[name] = id
   109  	cnt.ids[id] = name
   110  }
   111  
   112  // Loads all stored container from storage
   113  func (cnt *Containers) load(storage istorage.IAppStorage, versions *vers.Versions) (err error) {
   114  
   115  	ver := versions.Get(vers.SysContainersVersion)
   116  	switch ver {
   117  	case vers.UnknownVersion: // no sys.Container storage exists
   118  		return nil
   119  	case ver01:
   120  		return cnt.load01(storage)
   121  	}
   122  
   123  	return fmt.Errorf("unknown version of system Containers view (%v): %w", ver, vers.ErrorInvalidVersion)
   124  }
   125  
   126  // Loads all stored containers from storage version ver01
   127  func (cnt *Containers) load01(storage istorage.IAppStorage) error {
   128  
   129  	readName := func(cCols, value []byte) error {
   130  		name := string(cCols)
   131  		if ok, err := appdef.ValidIdent(name); !ok {
   132  			return err
   133  		}
   134  		id := ContainerID(binary.BigEndian.Uint16(value))
   135  		if id == NullContainerID {
   136  			return nil // deleted Container
   137  		}
   138  
   139  		if id <= ContainerNameIDSysLast {
   140  			return fmt.Errorf("unexpected ID (%v) is loaded from system Containers view: %w", id, ErrWrongContainerID)
   141  		}
   142  
   143  		cnt.containers[name] = id
   144  		cnt.ids[id] = name
   145  
   146  		if cnt.lastID < id {
   147  			cnt.lastID = id
   148  		}
   149  
   150  		return nil
   151  	}
   152  
   153  	pKey := utils.ToBytes(consts.SysView_Containers, ver01)
   154  	return storage.Read(context.Background(), pKey, nil, nil, readName)
   155  }
   156  
   157  // Stores all known container to storage
   158  func (cnt *Containers) store(storage istorage.IAppStorage, versions *vers.Versions) (err error) {
   159  	pKey := utils.ToBytes(consts.SysView_Containers, latestVersion)
   160  
   161  	batch := make([]istorage.BatchItem, 0)
   162  	for name, id := range cnt.containers {
   163  		if name == "" {
   164  			continue // skip NullContainerID
   165  		}
   166  		item := istorage.BatchItem{
   167  			PKey:  pKey,
   168  			CCols: []byte(name),
   169  			Value: utils.ToBytes(id),
   170  		}
   171  		batch = append(batch, item)
   172  	}
   173  
   174  	if err = storage.PutBatch(batch); err != nil {
   175  		return fmt.Errorf("error store application container IDs to storage: %w", err)
   176  	}
   177  
   178  	if ver := versions.Get(vers.SysContainersVersion); ver != latestVersion {
   179  		if err = versions.Put(vers.SysContainersVersion, latestVersion); err != nil {
   180  			return fmt.Errorf("error store system Containers view version: %w", err)
   181  		}
   182  	}
   183  
   184  	cnt.changes = 0
   185  	return nil
   186  }