summary refs log tree commit diff stats
path: root/registry/user.go
diff options
context:
space:
mode:
Diffstat (limited to 'registry/user.go')
-rw-r--r--registry/user.go270
1 files changed, 270 insertions, 0 deletions
diff --git a/registry/user.go b/registry/user.go
new file mode 100644
index 0000000..329b6e3
--- /dev/null
+++ b/registry/user.go
@@ -0,0 +1,270 @@
+/*
+Copyright (c) 2019 Ben Morrison (gbmor)
+
+This file is part of Registry.
+
+Registry is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Registry is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Registry.  If not, see <https://www.gnu.org/licenses/>.
+*/
+
+package registry // import "git.sr.ht/~gbmor/getwtxt/registry"
+
+import (
+	"fmt"
+	"net"
+	"strings"
+	"sync"
+	"time"
+)
+
+// AddUser inserts a new user into the Registry.
+func (registry *Registry) AddUser(nickname, urlKey string, ipAddress net.IP, statuses TimeMap) error {
+
+	if registry == nil {
+		return fmt.Errorf("can't add user to uninitialized registry")
+
+	} else if nickname == "" || urlKey == "" {
+		return fmt.Errorf("both URL and Nick must be specified")
+
+	} else if !strings.HasPrefix(urlKey, "http") {
+		return fmt.Errorf("invalid URL: %v", urlKey)
+	}
+
+	registry.Mu.Lock()
+	defer registry.Mu.Unlock()
+
+	if _, ok := registry.Users[urlKey]; ok {
+		return fmt.Errorf("user %v already exists", urlKey)
+	}
+
+	registry.Users[urlKey] = &User{
+		Mu:           sync.RWMutex{},
+		Nick:         nickname,
+		URL:          urlKey,
+		LastModified: "",
+		IP:           ipAddress,
+		Date:         time.Now().Format(time.RFC3339),
+		Status:       statuses}
+
+	return nil
+}
+
+// Put inserts a given User into an Registry. The User
+// being pushed need only have the URL field filled.
+// All other fields may be empty.
+// This can be destructive: an existing User in the
+// Registry will be overwritten if its User.URL is the
+// same as the User.URL being pushed.
+func (registry *Registry) Put(user *User) error {
+	if user == nil {
+		return fmt.Errorf("can't push nil data to registry")
+	}
+	if registry == nil || registry.Users == nil {
+		return fmt.Errorf("can't push data to registry: registry uninitialized")
+	}
+	user.Mu.RLock()
+	if user.URL == "" {
+		user.Mu.RUnlock()
+		return fmt.Errorf("can't push data to registry: missing URL for key")
+	}
+	urlKey := user.URL
+	registry.Mu.Lock()
+	registry.Users[urlKey] = user
+	registry.Mu.Unlock()
+	user.Mu.RUnlock()
+
+	return nil
+}
+
+// Get returns the User associated with the
+// provided URL key in the Registry.
+func (registry *Registry) Get(urlKey string) (*User, error) {
+	if registry == nil {
+		return nil, fmt.Errorf("can't pop from nil registry")
+	}
+	if urlKey == "" {
+		return nil, fmt.Errorf("can't pop unless provided a key")
+	}
+
+	registry.Mu.RLock()
+	defer registry.Mu.RUnlock()
+
+	if _, ok := registry.Users[urlKey]; !ok {
+		return nil, fmt.Errorf("provided url key doesn't exist in registry")
+	}
+
+	registry.Users[urlKey].Mu.RLock()
+	userGot := registry.Users[urlKey]
+	registry.Users[urlKey].Mu.RUnlock()
+
+	return userGot, nil
+}
+
+// DelUser removes a user and all associated data from
+// the Registry.
+func (registry *Registry) DelUser(urlKey string) error {
+
+	if registry == nil {
+		return fmt.Errorf("can't delete user from empty registry")
+
+	} else if urlKey == "" {
+		return fmt.Errorf("can't delete blank user")
+
+	} else if !strings.HasPrefix(urlKey, "http") {
+		return fmt.Errorf("invalid URL: %v", urlKey)
+	}
+
+	registry.Mu.Lock()
+	defer registry.Mu.Unlock()
+
+	if _, ok := registry.Users[urlKey]; !ok {
+		return fmt.Errorf("can't delete user %v, user doesn't exist", urlKey)
+	}
+
+	delete(registry.Users, urlKey)
+
+	return nil
+}
+
+// UpdateUser scrapes an existing user's remote twtxt.txt
+// file. Any new statuses are added to the user's entry
+// in the Registry. If the remote twtxt data's reported
+// Content-Length does not differ from what is stored,
+// an error is returned.
+func (registry *Registry) UpdateUser(urlKey string) error {
+	if urlKey == "" || !strings.HasPrefix(urlKey, "http") {
+		return fmt.Errorf("invalid URL: %v", urlKey)
+	}
+
+	diff, err := registry.DiffTwtxt(urlKey)
+	if err != nil {
+		return err
+	} else if !diff {
+		return fmt.Errorf("no new statuses available for %v", urlKey)
+	}
+
+	out, isRemoteRegistry, err := GetTwtxt(urlKey, registry.HTTPClient)
+	if err != nil {
+		return err
+	}
+
+	if isRemoteRegistry {
+		return fmt.Errorf("attempting to update registry URL - users should be updated individually")
+	}
+
+	registry.Mu.Lock()
+	defer registry.Mu.Unlock()
+	user := registry.Users[urlKey]
+
+	user.Mu.Lock()
+	defer user.Mu.Unlock()
+	nick := user.Nick
+
+	data, err := ParseUserTwtxt(out, nick, urlKey)
+	if err != nil {
+		return err
+	}
+
+	for i, e := range data {
+		user.Status[i] = e
+	}
+
+	registry.Users[urlKey] = user
+
+	return nil
+}
+
+// CrawlRemoteRegistry scrapes all nicknames and user URLs
+// from a provided registry. The urlKey passed to this function
+// must be in the form of https://registry.example.com/api/plain/users
+func (registry *Registry) CrawlRemoteRegistry(urlKey string) error {
+	if urlKey == "" || !strings.HasPrefix(urlKey, "http") {
+		return fmt.Errorf("invalid URL: %v", urlKey)
+	}
+
+	out, isRemoteRegistry, err := GetTwtxt(urlKey, registry.HTTPClient)
+	if err != nil {
+		return err
+	}
+
+	if !isRemoteRegistry {
+		return fmt.Errorf("can't add single user via call to CrawlRemoteRegistry")
+	}
+
+	users, err := ParseRegistryTwtxt(out)
+	if err != nil {
+		return err
+	}
+
+	// only add new users so we don't overwrite data
+	// we already have (and lose statuses, etc)
+	registry.Mu.Lock()
+	defer registry.Mu.Unlock()
+	for _, e := range users {
+		if _, ok := registry.Users[e.URL]; !ok {
+			registry.Users[e.URL] = e
+		}
+	}
+
+	return nil
+}
+
+// GetUserStatuses returns a TimeMap containing single user's statuses
+func (registry *Registry) GetUserStatuses(urlKey string) (TimeMap, error) {
+	if registry == nil {
+		return nil, fmt.Errorf("can't get statuses from an empty registry")
+	} else if urlKey == "" || !strings.HasPrefix(urlKey, "http") {
+		return nil, fmt.Errorf("invalid URL: %v", urlKey)
+	}
+
+	registry.Mu.RLock()
+	defer registry.Mu.RUnlock()
+	if _, ok := registry.Users[urlKey]; !ok {
+		return nil, fmt.Errorf("can't retrieve statuses of nonexistent user")
+	}
+
+	registry.Users[urlKey].Mu.RLock()
+	status := registry.Users[urlKey].Status
+	registry.Users[urlKey].Mu.RUnlock()
+
+	return status, nil
+}
+
+// GetStatuses returns a TimeMap containing all statuses
+// from all users in the Registry.
+func (registry *Registry) GetStatuses() (TimeMap, error) {
+	if registry == nil {
+		return nil, fmt.Errorf("can't get statuses from an empty registry")
+	}
+
+	statuses := NewTimeMap()
+
+	registry.Mu.RLock()
+	defer registry.Mu.RUnlock()
+
+	for _, v := range registry.Users {
+		v.Mu.RLock()
+		if v.Status == nil || len(v.Status) == 0 {
+			v.Mu.RUnlock()
+			continue
+		}
+		for a, b := range v.Status {
+			if _, ok := v.Status[a]; ok {
+				statuses[a] = b
+			}
+		}
+		v.Mu.RUnlock()
+	}
+
+	return statuses, nil
+}