github.com/safing/portbase@v0.19.5/updater/state.go (about)

     1  package updater
     2  
     3  import (
     4  	"sort"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/safing/portbase/utils"
     9  )
    10  
    11  // Registry States.
    12  const (
    13  	StateReady       = "ready"       // Default idle state.
    14  	StateChecking    = "checking"    // Downloading indexes.
    15  	StateDownloading = "downloading" // Downloading updates.
    16  	StateFetching    = "fetching"    // Fetching a single file.
    17  )
    18  
    19  // RegistryState describes the registry state.
    20  type RegistryState struct {
    21  	sync.Mutex
    22  	reg *ResourceRegistry
    23  
    24  	// ID holds the ID of the state the registry is currently in.
    25  	ID string
    26  
    27  	// Details holds further information about the current state.
    28  	Details any
    29  
    30  	// Updates holds generic information about the current status of pending
    31  	// and recently downloaded updates.
    32  	Updates UpdateState
    33  
    34  	// operationLock locks the operation of any state changing operation.
    35  	// This is separate from the registry lock, which locks access to the
    36  	// registry struct.
    37  	operationLock sync.Mutex
    38  }
    39  
    40  // StateDownloadingDetails holds details of the downloading state.
    41  type StateDownloadingDetails struct {
    42  	// Resources holds the resource IDs that are being downloaded.
    43  	Resources []string
    44  
    45  	// FinishedUpTo holds the index of Resources that is currently being
    46  	// downloaded. Previous resources have finished downloading.
    47  	FinishedUpTo int
    48  }
    49  
    50  // UpdateState holds generic information about the current status of pending
    51  // and recently downloaded updates.
    52  type UpdateState struct {
    53  	// LastCheckAt holds the time of the last update check.
    54  	LastCheckAt *time.Time
    55  	// LastCheckError holds the error of the last check.
    56  	LastCheckError error
    57  	// PendingDownload holds the resources that are pending download.
    58  	PendingDownload []string
    59  
    60  	// LastDownloadAt holds the time when resources were downloaded the last time.
    61  	LastDownloadAt *time.Time
    62  	// LastDownloadError holds the error of the last download.
    63  	LastDownloadError error
    64  	// LastDownload holds the resources that we downloaded the last time updates
    65  	// were downloaded.
    66  	LastDownload []string
    67  
    68  	// LastSuccessAt holds the time of the last successful update (check).
    69  	LastSuccessAt *time.Time
    70  }
    71  
    72  // GetState returns the current registry state.
    73  // The returned data must not be modified.
    74  func (reg *ResourceRegistry) GetState() RegistryState {
    75  	reg.state.Lock()
    76  	defer reg.state.Unlock()
    77  
    78  	return RegistryState{
    79  		ID:      reg.state.ID,
    80  		Details: reg.state.Details,
    81  		Updates: reg.state.Updates,
    82  	}
    83  }
    84  
    85  // StartOperation starts an operation.
    86  func (s *RegistryState) StartOperation(id string) bool {
    87  	defer s.notify()
    88  
    89  	s.operationLock.Lock()
    90  
    91  	s.Lock()
    92  	defer s.Unlock()
    93  
    94  	s.ID = id
    95  	return true
    96  }
    97  
    98  // UpdateOperationDetails updates the details of an operation.
    99  // The supplied struct should be a copy and must not be changed after calling
   100  // this function.
   101  func (s *RegistryState) UpdateOperationDetails(details any) {
   102  	defer s.notify()
   103  
   104  	s.Lock()
   105  	defer s.Unlock()
   106  
   107  	s.Details = details
   108  }
   109  
   110  // EndOperation ends an operation.
   111  func (s *RegistryState) EndOperation() {
   112  	defer s.notify()
   113  	defer s.operationLock.Unlock()
   114  
   115  	s.Lock()
   116  	defer s.Unlock()
   117  
   118  	s.ID = StateReady
   119  	s.Details = nil
   120  }
   121  
   122  // ReportUpdateCheck reports an update check to the registry state.
   123  func (s *RegistryState) ReportUpdateCheck(pendingDownload []string, failed error) {
   124  	defer s.notify()
   125  
   126  	sort.Strings(pendingDownload)
   127  
   128  	s.Lock()
   129  	defer s.Unlock()
   130  
   131  	now := time.Now()
   132  	s.Updates.LastCheckAt = &now
   133  	s.Updates.LastCheckError = failed
   134  	s.Updates.PendingDownload = pendingDownload
   135  
   136  	if failed == nil {
   137  		s.Updates.LastSuccessAt = &now
   138  	}
   139  }
   140  
   141  // ReportDownloads reports downloaded updates to the registry state.
   142  func (s *RegistryState) ReportDownloads(downloaded []string, failed error) {
   143  	defer s.notify()
   144  
   145  	sort.Strings(downloaded)
   146  
   147  	s.Lock()
   148  	defer s.Unlock()
   149  
   150  	now := time.Now()
   151  	s.Updates.LastDownloadAt = &now
   152  	s.Updates.LastDownloadError = failed
   153  	s.Updates.LastDownload = downloaded
   154  
   155  	// Remove downloaded resources from the pending list.
   156  	if len(s.Updates.PendingDownload) > 0 {
   157  		newPendingDownload := make([]string, 0, len(s.Updates.PendingDownload))
   158  		for _, pending := range s.Updates.PendingDownload {
   159  			if !utils.StringInSlice(downloaded, pending) {
   160  				newPendingDownload = append(newPendingDownload, pending)
   161  			}
   162  		}
   163  		s.Updates.PendingDownload = newPendingDownload
   164  	}
   165  
   166  	if failed == nil {
   167  		s.Updates.LastSuccessAt = &now
   168  	}
   169  }
   170  
   171  func (s *RegistryState) notify() {
   172  	switch {
   173  	case s.reg == nil:
   174  		return
   175  	case s.reg.StateNotifyFunc == nil:
   176  		return
   177  	}
   178  
   179  	s.reg.StateNotifyFunc(s)
   180  }