github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/qct/downloader/api.go (about) 1 package downloader 2 3 import ( 4 "context" 5 "sync" 6 7 quickchain "github.com/quickchainproject/quickchain" 8 "github.com/quickchainproject/quickchain/event" 9 "github.com/quickchainproject/quickchain/rpc" 10 ) 11 12 // PublicDownloaderAPI provides an API which gives information about the current synchronisation status. 13 // It offers only methods that operates on data that can be available to anyone without security risks. 14 type PublicDownloaderAPI struct { 15 d *Downloader 16 mux *event.TypeMux 17 installSyncSubscription chan chan interface{} 18 uninstallSyncSubscription chan *uninstallSyncSubscriptionRequest 19 } 20 21 // NewPublicDownloaderAPI create a new PublicDownloaderAPI. The API has an internal event loop that 22 // listens for events from the downloader through the global event mux. In case it receives one of 23 // these events it broadcasts it to all syncing subscriptions that are installed through the 24 // installSyncSubscription channel. 25 func NewPublicDownloaderAPI(d *Downloader, m *event.TypeMux) *PublicDownloaderAPI { 26 api := &PublicDownloaderAPI{ 27 d: d, 28 mux: m, 29 installSyncSubscription: make(chan chan interface{}), 30 uninstallSyncSubscription: make(chan *uninstallSyncSubscriptionRequest), 31 } 32 33 go api.eventLoop() 34 35 return api 36 } 37 38 // eventLoop runs a loop until the event mux closes. It will install and uninstall new 39 // sync subscriptions and broadcasts sync status updates to the installed sync subscriptions. 40 func (api *PublicDownloaderAPI) eventLoop() { 41 var ( 42 sub = api.mux.Subscribe(StartEvent{}, DoneEvent{}, FailedEvent{}) 43 syncSubscriptions = make(map[chan interface{}]struct{}) 44 ) 45 46 for { 47 select { 48 case i := <-api.installSyncSubscription: 49 syncSubscriptions[i] = struct{}{} 50 case u := <-api.uninstallSyncSubscription: 51 delete(syncSubscriptions, u.c) 52 close(u.uninstalled) 53 case event := <-sub.Chan(): 54 if event == nil { 55 return 56 } 57 58 var notification interface{} 59 switch event.Data.(type) { 60 case StartEvent: 61 notification = &SyncingResult{ 62 Syncing: true, 63 Status: api.d.Progress(), 64 } 65 case DoneEvent, FailedEvent: 66 notification = false 67 } 68 // broadcast 69 for c := range syncSubscriptions { 70 c <- notification 71 } 72 } 73 } 74 } 75 76 // Syncing provides information when this nodes starts synchronising with the Quickchain network and when it's finished. 77 func (api *PublicDownloaderAPI) Syncing(ctx context.Context) (*rpc.Subscription, error) { 78 notifier, supported := rpc.NotifierFromContext(ctx) 79 if !supported { 80 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 81 } 82 83 rpcSub := notifier.CreateSubscription() 84 85 go func() { 86 statuses := make(chan interface{}) 87 sub := api.SubscribeSyncStatus(statuses) 88 89 for { 90 select { 91 case status := <-statuses: 92 notifier.Notify(rpcSub.ID, status) 93 case <-rpcSub.Err(): 94 sub.Unsubscribe() 95 return 96 case <-notifier.Closed(): 97 sub.Unsubscribe() 98 return 99 } 100 } 101 }() 102 103 return rpcSub, nil 104 } 105 106 // SyncingResult provides information about the current synchronisation status for this node. 107 type SyncingResult struct { 108 Syncing bool `json:"syncing"` 109 Status quickchain.SyncProgress `json:"status"` 110 } 111 112 // uninstallSyncSubscriptionRequest uninstalles a syncing subscription in the API event loop. 113 type uninstallSyncSubscriptionRequest struct { 114 c chan interface{} 115 uninstalled chan interface{} 116 } 117 118 // SyncStatusSubscription represents a syncing subscription. 119 type SyncStatusSubscription struct { 120 api *PublicDownloaderAPI // register subscription in event loop of this api instance 121 c chan interface{} // channel where events are broadcasted to 122 unsubOnce sync.Once // make sure unsubscribe logic is executed once 123 } 124 125 // Unsubscribe uninstalls the subscription from the DownloadAPI event loop. 126 // The status channel that was passed to subscribeSyncStatus isn't used anymore 127 // after this method returns. 128 func (s *SyncStatusSubscription) Unsubscribe() { 129 s.unsubOnce.Do(func() { 130 req := uninstallSyncSubscriptionRequest{s.c, make(chan interface{})} 131 s.api.uninstallSyncSubscription <- &req 132 133 for { 134 select { 135 case <-s.c: 136 // drop new status events until uninstall confirmation 137 continue 138 case <-req.uninstalled: 139 return 140 } 141 } 142 }) 143 } 144 145 // SubscribeSyncStatus creates a subscription that will broadcast new synchronisation updates. 146 // The given channel must receive interface values, the result can either 147 func (api *PublicDownloaderAPI) SubscribeSyncStatus(status chan interface{}) *SyncStatusSubscription { 148 api.installSyncSubscription <- status 149 return &SyncStatusSubscription{api: api, c: status} 150 }