github.com/core-coin/go-core/v2@v2.1.9/xcb/downloader/api.go (about)

     1  // Copyright 2015 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-core library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package downloader
    18  
    19  import (
    20  	"context"
    21  	"sync"
    22  
    23  	core "github.com/core-coin/go-core/v2"
    24  
    25  	"github.com/core-coin/go-core/v2/event"
    26  	"github.com/core-coin/go-core/v2/rpc"
    27  )
    28  
    29  // PublicDownloaderAPI provides an API which gives information about the current synchronisation status.
    30  // It offers only methods that operates on data that can be available to anyone without security risks.
    31  type PublicDownloaderAPI struct {
    32  	d                         *Downloader
    33  	mux                       *event.TypeMux
    34  	installSyncSubscription   chan chan interface{}
    35  	uninstallSyncSubscription chan *uninstallSyncSubscriptionRequest
    36  }
    37  
    38  // NewPublicDownloaderAPI create a new PublicDownloaderAPI. The API has an internal event loop that
    39  // listens for events from the downloader through the global event mux. In case it receives one of
    40  // these events it broadcasts it to all syncing subscriptions that are installed through the
    41  // installSyncSubscription channel.
    42  func NewPublicDownloaderAPI(d *Downloader, m *event.TypeMux) *PublicDownloaderAPI {
    43  	api := &PublicDownloaderAPI{
    44  		d:                         d,
    45  		mux:                       m,
    46  		installSyncSubscription:   make(chan chan interface{}),
    47  		uninstallSyncSubscription: make(chan *uninstallSyncSubscriptionRequest),
    48  	}
    49  
    50  	go api.eventLoop()
    51  
    52  	return api
    53  }
    54  
    55  // eventLoop runs a loop until the event mux closes. It will install and uninstall new
    56  // sync subscriptions and broadcasts sync status updates to the installed sync subscriptions.
    57  func (api *PublicDownloaderAPI) eventLoop() {
    58  	var (
    59  		sub               = api.mux.Subscribe(StartEvent{}, DoneEvent{}, FailedEvent{})
    60  		syncSubscriptions = make(map[chan interface{}]struct{})
    61  	)
    62  
    63  	for {
    64  		select {
    65  		case i := <-api.installSyncSubscription:
    66  			syncSubscriptions[i] = struct{}{}
    67  		case u := <-api.uninstallSyncSubscription:
    68  			delete(syncSubscriptions, u.c)
    69  			close(u.uninstalled)
    70  		case event := <-sub.Chan():
    71  			if event == nil {
    72  				return
    73  			}
    74  
    75  			var notification interface{}
    76  			switch event.Data.(type) {
    77  			case StartEvent:
    78  				notification = &SyncingResult{
    79  					Syncing: true,
    80  					Status:  api.d.Progress(),
    81  				}
    82  			case DoneEvent, FailedEvent:
    83  				notification = false
    84  			}
    85  			// broadcast
    86  			for c := range syncSubscriptions {
    87  				c <- notification
    88  			}
    89  		}
    90  	}
    91  }
    92  
    93  // Syncing provides information when this nodes starts synchronising with the Core network and when it's finished.
    94  func (api *PublicDownloaderAPI) Syncing(ctx context.Context) (*rpc.Subscription, error) {
    95  	notifier, supported := rpc.NotifierFromContext(ctx)
    96  	if !supported {
    97  		return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
    98  	}
    99  
   100  	rpcSub := notifier.CreateSubscription()
   101  
   102  	go func() {
   103  		statuses := make(chan interface{})
   104  		sub := api.SubscribeSyncStatus(statuses)
   105  
   106  		for {
   107  			select {
   108  			case status := <-statuses:
   109  				notifier.Notify(rpcSub.ID, status)
   110  			case <-rpcSub.Err():
   111  				sub.Unsubscribe()
   112  				return
   113  			case <-notifier.Closed():
   114  				sub.Unsubscribe()
   115  				return
   116  			}
   117  		}
   118  	}()
   119  
   120  	return rpcSub, nil
   121  }
   122  
   123  // SyncingResult provides information about the current synchronisation status for this node.
   124  type SyncingResult struct {
   125  	Syncing bool              `json:"syncing"`
   126  	Status  core.SyncProgress `json:"status"`
   127  }
   128  
   129  // uninstallSyncSubscriptionRequest uninstalles a syncing subscription in the API event loop.
   130  type uninstallSyncSubscriptionRequest struct {
   131  	c           chan interface{}
   132  	uninstalled chan interface{}
   133  }
   134  
   135  // SyncStatusSubscription represents a syncing subscription.
   136  type SyncStatusSubscription struct {
   137  	api       *PublicDownloaderAPI // register subscription in event loop of this api instance
   138  	c         chan interface{}     // channel where events are broadcasted to
   139  	unsubOnce sync.Once            // make sure unsubscribe logic is executed once
   140  }
   141  
   142  // Unsubscribe uninstalls the subscription from the DownloadAPI event loop.
   143  // The status channel that was passed to subscribeSyncStatus isn't used anymore
   144  // after this method returns.
   145  func (s *SyncStatusSubscription) Unsubscribe() {
   146  	s.unsubOnce.Do(func() {
   147  		req := uninstallSyncSubscriptionRequest{s.c, make(chan interface{})}
   148  		s.api.uninstallSyncSubscription <- &req
   149  
   150  		for {
   151  			select {
   152  			case <-s.c:
   153  				// drop new status events until uninstall confirmation
   154  				continue
   155  			case <-req.uninstalled:
   156  				return
   157  			}
   158  		}
   159  	})
   160  }
   161  
   162  // SubscribeSyncStatus creates a subscription that will broadcast new synchronisation updates.
   163  // The given channel must receive interface values, the result can either
   164  func (api *PublicDownloaderAPI) SubscribeSyncStatus(status chan interface{}) *SyncStatusSubscription {
   165  	api.installSyncSubscription <- status
   166  	return &SyncStatusSubscription{api: api, c: status}
   167  }