|
- package storage
-
- import (
- "cwtch.im/cwtch/event"
- "cwtch.im/cwtch/model"
- "encoding/json"
- "git.openprivacy.ca/openprivacy/libricochet-go/log"
- "os"
- "time"
- )
-
- const groupIDLen = 32
- const peerIDLen = 56
- const profileFilename = "profile"
-
- type profileStore struct {
- fs FileStore
- streamStores map[string]StreamStore // map [groupId|onion] StreamStore
- directory string
- password string
- profile *model.Profile
- eventManager event.Manager
- queue event.Queue
- writer bool
- }
-
- // ProfileStore is an interface to managing the storage of Cwtch Profiles
- type ProfileStore interface {
- Load() error
- Shutdown()
- Delete()
- GetProfileCopy(timeline bool) *model.Profile
- GetNewPeerMessage() *event.Event
- GetStatusMessages() []*event.Event
- }
-
- // NewProfileWriterStore returns a profile store backed by a filestore listening for events and saving them
- // directory should be $appDir/profiles/$rand
- func NewProfileWriterStore(eventManager event.Manager, directory, password string, profile *model.Profile) ProfileStore {
- os.Mkdir(directory, 0700)
- ps := &profileStore{fs: NewFileStore(directory, profileFilename, password), password: password, directory: directory, profile: profile, eventManager: eventManager, streamStores: map[string]StreamStore{}, writer: true}
- //ps.queue = event.NewQueue(100)
- ps.queue = event.NewQueue()
- if profile != nil {
- ps.save()
- }
- go ps.eventHandler()
-
- ps.eventManager.Subscribe(event.BlockPeer, ps.queue)
- ps.eventManager.Subscribe(event.UnblockPeer, ps.queue)
- ps.eventManager.Subscribe(event.PeerCreated, ps.queue)
- ps.eventManager.Subscribe(event.GroupCreated, ps.queue)
- ps.eventManager.Subscribe(event.SetProfileName, ps.queue)
- ps.eventManager.Subscribe(event.SetAttribute, ps.queue)
- ps.eventManager.Subscribe(event.SetPeerAttribute, ps.queue)
- ps.eventManager.Subscribe(event.SetGroupAttribute, ps.queue)
- ps.eventManager.Subscribe(event.AcceptGroupInvite, ps.queue)
- ps.eventManager.Subscribe(event.NewGroupInvite, ps.queue)
- ps.eventManager.Subscribe(event.NewMessageFromGroup, ps.queue)
- ps.eventManager.Subscribe(event.PeerStateChange, ps.queue)
- ps.eventManager.Subscribe(event.ServerStateChange, ps.queue)
- ps.eventManager.Subscribe(event.DeleteContact, ps.queue)
- ps.eventManager.Subscribe(event.DeleteGroup, ps.queue)
- ps.eventManager.Subscribe(event.ChangePassword, ps.queue)
-
- return ps
- }
-
- // ReadProfile reads a profile from storqage and returns the profile
- // directory should be $appDir/profiles/$rand
- func ReadProfile(directory, password string) (*model.Profile, error) {
- os.Mkdir(directory, 0700)
- ps := &profileStore{fs: NewFileStore(directory, profileFilename, password), password: password, directory: directory, profile: nil, eventManager: nil, streamStores: map[string]StreamStore{}, writer: true}
-
- err := ps.Load()
- if err != nil {
- return nil, err
- }
-
- profile := ps.GetProfileCopy(true)
-
- return profile, nil
- }
-
- // NewProfile creates a new profile for use in the profile store.
- func NewProfile(name string) *model.Profile {
- profile := model.GenerateNewProfile(name)
- return profile
- }
-
- // GetNewPeerMessage is for AppService to call on Reload events, to reseed the AppClient with the loaded peers
- func (ps *profileStore) GetNewPeerMessage() *event.Event {
- message := event.NewEventList(event.NewPeer, event.Identity, ps.profile.LocalID, event.Password, ps.password, event.Status, "running")
- return &message
- }
-
- func (ps *profileStore) GetStatusMessages() []*event.Event {
- messages := []*event.Event{}
- for _, contact := range ps.profile.Contacts {
- message := event.NewEvent(event.PeerStateChange, map[event.Field]string{
- event.RemotePeer: string(contact.Onion),
- event.ConnectionState: contact.State,
- })
- messages = append(messages, &message)
- }
-
- doneServers := make(map[string]bool)
- for _, group := range ps.profile.Groups {
- if _, exists := doneServers[group.GroupServer]; !exists {
- message := event.NewEvent(event.ServerStateChange, map[event.Field]string{
- event.GroupServer: string(group.GroupServer),
- event.ConnectionState: group.State,
- })
- messages = append(messages, &message)
- doneServers[group.GroupServer] = true
- }
- }
-
- return messages
- }
-
- func (ps *profileStore) ChangePassword(oldpass, newpass, eventID string) {
- if oldpass != ps.password {
- ps.eventManager.Publish(event.NewEventList(event.ChangePasswordError, event.Error, "Supplied current password does not match", event.EventID, eventID))
- return
- }
-
- newStreamStores := map[string]StreamStore{}
- idToNewLocalID := map[string]string{}
-
- // Generate all new StreamStores with the new password and write all the old StreamStore data into these ones
- for ssid, ss := range ps.streamStores {
- // New ss with new pass and new localID
- newlocalID := model.GenerateRandomID()
- idToNewLocalID[ssid] = newlocalID
-
- newSS := NewStreamStore(ps.directory, newlocalID, newpass)
- newStreamStores[ssid] = newSS
-
- // write whole store
- messages := ss.Read()
- newSS.WriteN(messages)
- }
-
- // Switch over
- oldStreamStores := ps.streamStores
- ps.streamStores = newStreamStores
- for ssid, newLocalID := range idToNewLocalID {
- if len(ssid) == groupIDLen {
- ps.profile.Groups[ssid].LocalID = newLocalID
- } else {
- ps.profile.Contacts[ssid].LocalID = newLocalID
- }
- }
-
- ps.password = newpass
- ps.fs.ChangePassword(newpass)
- ps.save()
-
- // Clean up
- for _, oldss := range oldStreamStores {
- oldss.Delete()
- }
-
- ps.eventManager.Publish(event.NewEventList(event.ChangePasswordSuccess, event.EventID, eventID))
- return
- }
-
- func (ps *profileStore) save() error {
- if ps.writer {
- bytes, _ := json.Marshal(ps.profile)
- return ps.fs.Write(bytes)
- }
- return nil
- }
-
- // Read instantiates a cwtchPeer from the file store
- func (ps *profileStore) Load() error {
- decrypted, err := ps.fs.Read()
- if err != nil {
- return err
- }
- cp := new(model.Profile)
- err = json.Unmarshal(decrypted, &cp)
- if err == nil {
- ps.profile = cp
-
- for gid, group := range cp.Groups {
- ss := NewStreamStore(ps.directory, group.LocalID, ps.password)
-
- cp.Groups[gid].Timeline.SetMessages(ss.Read())
- ps.streamStores[group.GroupID] = ss
- }
- }
-
- return err
- }
-
- func (ps *profileStore) GetProfileCopy(timeline bool) *model.Profile {
- return ps.profile.GetCopy(timeline)
- }
-
- func (ps *profileStore) eventHandler() {
- log.Infoln("eventHandler()!")
- for {
- ev := ps.queue.Next()
- log.Infof("eventHandler event %v\n", ev)
-
- switch ev.EventType {
- case event.BlockPeer:
- contact, exists := ps.profile.GetContact(ev.Data[event.RemotePeer])
- if exists {
- contact.Blocked = true
- ps.save()
- }
- case event.UnblockPeer:
- contact, exists := ps.profile.GetContact(ev.Data[event.RemotePeer])
- if exists {
- contact.Blocked = false
- ps.save()
- }
- case event.PeerCreated:
- var pp *model.PublicProfile
- json.Unmarshal([]byte(ev.Data[event.Data]), &pp)
- ps.profile.AddContact(ev.Data[event.RemotePeer], pp)
- // TODO: configure - allow peers to be configured to turn on limited storage
- /*ss := NewStreamStore(ps.directory, pp.LocalID, ps.password)
- pp.Timeline.SetMessages(ss.Read())
- ps.streamStores[pp.Onion] = ss
- ps.save()*/
- case event.GroupCreated:
- var group *model.Group
- json.Unmarshal([]byte(ev.Data[event.Data]), &group)
- ps.profile.AddGroup(group)
- ps.streamStores[group.GroupID] = NewStreamStore(ps.directory, group.LocalID, ps.password)
- ps.save()
- case event.SetProfileName:
- ps.profile.Name = ev.Data[event.ProfileName]
- ps.profile.SetAttribute("name", ev.Data[event.ProfileName])
- ps.save()
- case event.SetAttribute:
- ps.profile.SetAttribute(ev.Data[event.Key], ev.Data[event.Data])
- ps.save()
- case event.SetPeerAttribute:
- contact, exists := ps.profile.GetContact(ev.Data[event.RemotePeer])
- if exists {
- contact.SetAttribute(ev.Data[event.Key], ev.Data[event.Data])
- ps.save()
- } else {
- log.Errorf("error setting attribute on peer %v peer does not exist", ev)
- }
- case event.SetGroupAttribute:
- group := ps.profile.GetGroup(ev.Data[event.GroupID])
- if group != nil {
- group.SetAttribute(ev.Data[event.Key], ev.Data[event.Data])
- ps.save()
- } else {
- log.Errorf("error setting attribute on group %v group does not exist", ev)
- }
- case event.AcceptGroupInvite:
- err := ps.profile.AcceptInvite(ev.Data[event.GroupID])
- if err == nil {
- ps.save()
- } else {
- log.Errorf("error accepting group invite")
- }
- case event.NewGroupInvite:
- gid, err := ps.profile.ProcessInvite(ev.Data[event.GroupInvite], ev.Data[event.RemotePeer])
- log.Errorf("gid: %v err:%v\n", gid, err)
- if err == nil {
- ps.save()
- group := ps.profile.Groups[gid]
- ps.streamStores[group.GroupID] = NewStreamStore(ps.directory, group.LocalID, ps.password)
- } else {
- log.Errorf("error storing new group invite: %v (%v)", err, ev)
- }
- case event.NewMessageFromGroup:
- groupid := ev.Data[event.GroupID]
- received, _ := time.Parse(time.RFC3339Nano, ev.Data[event.TimestampReceived])
- sent, _ := time.Parse(time.RFC3339Nano, ev.Data[event.TimestampSent])
- message := model.Message{Received: received, Timestamp: sent, Message: ev.Data[event.Data], PeerID: ev.Data[event.RemotePeer], Signature: []byte(ev.Data[event.Signature]), PreviousMessageSig: []byte(ev.Data[event.PreviousSignature])}
- ss, exists := ps.streamStores[groupid]
- if exists {
- ss.Write(message)
- } else {
- log.Errorf("error storing new group message: %v stream store does not exist", ev)
- }
- case event.PeerStateChange:
- if _, exists := ps.profile.Contacts[ev.Data[event.RemotePeer]]; exists {
- ps.profile.Contacts[ev.Data[event.RemotePeer]].State = ev.Data[event.ConnectionState]
- }
- case event.ServerStateChange:
- for _, group := range ps.profile.Groups {
- if group.GroupServer == ev.Data[event.GroupServer] {
- group.State = ev.Data[event.ConnectionState]
- }
- }
- case event.DeleteContact:
- onion := ev.Data[event.RemotePeer]
- ps.profile.DeleteContact(onion)
- ps.save()
- case event.DeleteGroup:
- groupID := ev.Data[event.GroupID]
- ps.profile.DeleteGroup(groupID)
- ps.save()
- ss, exists := ps.streamStores[groupID]
- if exists {
- ss.Delete()
- delete(ps.streamStores, groupID)
- }
- case event.ChangePassword:
- oldpass := ev.Data[event.Password]
- newpass := ev.Data[event.NewPassword]
- ps.ChangePassword(oldpass, newpass, ev.EventID)
- default:
- return
- }
-
- }
- }
-
- func (ps *profileStore) Shutdown() {
- if ps.queue != nil {
- ps.queue.Shutdown()
- }
- }
-
- func (ps *profileStore) Delete() {
- log.Debugf("Delete ProfileStore for %v\n", ps.profile.Onion)
-
- for _, ss := range ps.streamStores {
- ss.Delete()
- }
-
- ps.fs.Delete()
-
- err := os.RemoveAll(ps.directory)
- if err != nil {
- log.Errorf("ProfileStore Delete error on RemoveAll on %v was %v\n", ps.directory, err)
- }
- }
|