package app import ( "cwtch.im/cwtch/app/plugins" "cwtch.im/cwtch/event" "cwtch.im/cwtch/model" "cwtch.im/cwtch/protocol/connections" "cwtch.im/cwtch/storage" "cwtch.im/tapir/primitives" "git.openprivacy.ca/openprivacy/connectivity" "git.openprivacy.ca/openprivacy/log" "path" "strconv" "sync" ) type applicationService struct { applicationBridge appletACN appletPlugins storage map[string]storage.ProfileStore engines map[string]connections.Engine asmutex sync.Mutex } // ApplicationService is the back end of an application that manages engines and writing storage and communicates to an ApplicationClient by an IPCBridge type ApplicationService interface { Shutdown() } // NewAppService returns an ApplicationService that runs the backend of an app and communicates with a client by the supplied IPCBridge func NewAppService(acn connectivity.ACN, appDirectory string, bridge event.IPCBridge) ApplicationService { appService := &applicationService{storage: make(map[string]storage.ProfileStore), engines: make(map[string]connections.Engine), applicationBridge: applicationBridge{applicationCore: *newAppCore(appDirectory), bridge: bridge}} appService.appletACN.init(acn, appService.getACNStatusHandler()) appService.handle = appService.handleEvent go appService.listen() log.Infoln("Created new App Service") return appService } func (as *applicationService) handleEvent(ev *event.Event) { log.Infof("app Service handleEvent %v\n", ev.EventType) switch ev.EventType { case event.CreatePeer: profileName := ev.Data[event.ProfileName] password := ev.Data[event.Password] tag := ev.Data[event.Data] as.createPeer(profileName, password, tag) case event.DeletePeer: onion := ev.Data[event.Identity] as.deletePeer(onion) message := event.IPCMessage{Dest: DestApp, Message: *ev} as.bridge.Write(&message) case event.AddPeerPlugin: onion := ev.Data[event.Identity] pluginID, _ := strconv.Atoi(ev.Data[event.Data]) as.AddPlugin(onion, plugins.PluginID(pluginID), as.eventBuses[onion], as.acn) case event.LoadProfiles: password := ev.Data[event.Password] as.loadProfiles(password) case event.ReloadClient: for _, storage := range as.storage { peerMsg := *storage.GetNewPeerMessage() peerMsg.Data[event.Status] = event.StorageRunning message := event.IPCMessage{Dest: DestApp, Message: peerMsg} as.bridge.Write(&message) } message := event.IPCMessage{Dest: DestApp, Message: event.NewEventList(event.ReloadDone)} as.bridge.Write(&message) case event.ReloadPeer: onion := ev.Data[event.Identity] events := as.storage[onion].GetStatusMessages() for _, ev := range events { message := event.IPCMessage{Dest: onion, Message: *ev} as.bridge.Write(&message) } case event.GetACNStatus: prog, status := as.acn.GetBootstrapStatus() as.getACNStatusHandler()(prog, status) case event.ShutdownPeer: onion := ev.Data[event.Identity] as.ShutdownPeer(onion) } } func (as *applicationService) createPeer(name, password, tag string) { log.Infof("app Service create peer %v %v\n", name, password) profile, err := as.applicationCore.CreatePeer(name) as.eventBuses[profile.Onion] = event.IPCEventManagerFrom(as.bridge, profile.Onion, as.eventBuses[profile.Onion]) if err != nil { log.Errorf("Could not create Peer: %v\n", err) message := event.IPCMessage{Dest: DestApp, Message: event.NewEventList(event.PeerError, event.Error, err.Error())} as.bridge.Write(&message) return } if tag != "" { profile.SetAttribute(AttributeTag, tag) } profileStore := storage.CreateProfileWriterStore(as.eventBuses[profile.Onion], path.Join(as.directory, "profiles", profile.LocalID), password, profile) blockedPeers := profile.BlockedPeers() // TODO: Would be nice if ProtocolEngine did not need to explicitly be given the Private Key. identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey) engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, as.acn, as.eventBuses[profile.Onion], profile.GetContacts(), blockedPeers) as.storage[profile.Onion] = profileStore as.engines[profile.Onion] = engine peerMsg := *profileStore.GetNewPeerMessage() peerMsg.Data[event.Status] = event.StorageNew message := event.IPCMessage{Dest: DestApp, Message: peerMsg} as.bridge.Write(&message) } func (as *applicationService) loadProfiles(password string) { count := 0 as.applicationCore.LoadProfiles(password, false, func(profile *model.Profile, profileStore storage.ProfileStore) { as.eventBuses[profile.Onion] = event.IPCEventManagerFrom(as.bridge, profile.Onion, as.eventBuses[profile.Onion]) blockedPeers := profile.BlockedPeers() identity := primitives.InitializeIdentity(profile.Name, &profile.Ed25519PrivateKey, &profile.Ed25519PublicKey) engine := connections.NewProtocolEngine(identity, profile.Ed25519PrivateKey, as.acn, as.eventBuses[profile.Onion], profile.GetContacts(), blockedPeers) as.asmutex.Lock() as.storage[profile.Onion] = profileStore as.engines[profile.Onion] = engine as.asmutex.Unlock() peerMsg := *profileStore.GetNewPeerMessage() peerMsg.Data[event.Status] = event.StorageNew message := event.IPCMessage{Dest: DestApp, Message: peerMsg} as.bridge.Write(&message) count++ }) if count == 0 { message := event.IPCMessage{Dest: DestApp, Message: event.NewEventList(event.AppError, event.Error, event.AppErrLoaded0)} as.bridge.Write(&message) } } func (as *applicationService) getACNStatusHandler() func(int, string) { return func(progress int, status string) { progStr := strconv.Itoa(progress) as.bridge.Write(&event.IPCMessage{Dest: DestApp, Message: event.NewEventList(event.ACNStatus, event.Progreess, progStr, event.Status, status)}) as.applicationCore.coremutex.Lock() defer as.applicationCore.coremutex.Unlock() for _, bus := range as.eventBuses { bus.Publish(event.NewEventList(event.ACNStatus, event.Progreess, progStr, event.Status, status)) } } } func (as *applicationService) deletePeer(onion string) { as.asmutex.Lock() defer as.asmutex.Unlock() as.appletPlugins.ShutdownPeer(onion) as.plugins.Delete(onion) as.engines[onion].Shutdown() delete(as.engines, onion) as.storage[onion].Shutdown() as.storage[onion].Delete() delete(as.storage, onion) as.applicationCore.DeletePeer(onion) } func (as *applicationService) ShutdownPeer(onion string) { as.engines[onion].Shutdown() delete(as.engines, onion) as.storage[onion].Shutdown() delete(as.storage, onion) as.eventBuses[onion].Shutdown() delete(as.eventBuses, onion) } // Shutdown shuts down the application Service and all peer related backend parts func (as *applicationService) Shutdown() { log.Debugf("shutting down application service...") as.appletPlugins.Shutdown() for id := range as.engines { log.Debugf("shutting down application service peer engine %v", id) as.ShutdownPeer(id) } }