mirror of
https://github.com/matrix-org/gomatrix
synced 2025-12-19 04:38:03 +00:00
Move remaining Go-NEB code into gomatrix
This commit is contained in:
parent
652dacf366
commit
ca6f598808
3 changed files with 403 additions and 37 deletions
208
client.go
208
client.go
|
|
@ -4,26 +4,46 @@
|
|||
package gomatrix
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Client represents a Matrix client.
|
||||
type Client struct {
|
||||
HomeserverURL *url.URL // The base homeserver URL
|
||||
Prefix string // The API prefix eg '/_matrix/client/r0'
|
||||
UserID string // The user ID of the client. Used for forming HTTP paths which use the client's user ID.
|
||||
AccessToken string // The access_token for the client.
|
||||
syncingMutex sync.Mutex // protects syncingID
|
||||
syncingID uint32 // Identifies the current Sync. Only one Sync can be active at any given time.
|
||||
Client *http.Client // The underlying HTTP client which will be used to make HTTP requests.
|
||||
FilterStorer FilterStorer // Interface for saving and loading the filter ID for sync.
|
||||
NextBatchStorer NextBatchStorer // Interface for saving and loading the "next_batch" sync token.
|
||||
HomeserverURL *url.URL // The base homeserver URL
|
||||
Prefix string // The API prefix eg '/_matrix/client/r0'
|
||||
UserID string // The user ID of the client. Used for forming HTTP paths which use the client's user ID.
|
||||
AccessToken string // The access_token for the client.
|
||||
syncingMutex sync.Mutex // protects syncingID
|
||||
syncingID uint32 // Identifies the current Sync. Only one Sync can be active at any given time.
|
||||
Client *http.Client // The underlying HTTP client which will be used to make HTTP requests.
|
||||
Syncer Syncer // The thing which can process /sync responses
|
||||
// TODO: Worker and Rooms
|
||||
}
|
||||
|
||||
// HTTPError An HTTP Error response, which may wrap an underlying native Go Error.
|
||||
type HTTPError struct {
|
||||
WrappedError error
|
||||
Message string
|
||||
Code int
|
||||
}
|
||||
|
||||
func (e HTTPError) Error() string {
|
||||
var wrappedErrMsg string
|
||||
if e.WrappedError != nil {
|
||||
wrappedErrMsg = e.WrappedError.Error()
|
||||
}
|
||||
return fmt.Sprintf("%s: %d: %s", e.Message, e.Code, wrappedErrMsg)
|
||||
}
|
||||
|
||||
// BuildURL builds a URL with the Client's homserver/prefix/access_token set already.
|
||||
func (cli *Client) BuildURL(urlPath ...string) string {
|
||||
ps := []string{cli.Prefix}
|
||||
|
|
@ -58,6 +78,154 @@ func (cli *Client) BuildURLWithQuery(urlPath []string, urlQuery map[string]strin
|
|||
return u.String()
|
||||
}
|
||||
|
||||
// Sync starts syncing with the provided Homeserver. This function will block until a fatal /sync error occurs, so should
|
||||
// almost always be started as a new goroutine. If Sync() is called twice then the first sync will be stopped.
|
||||
func (cli *Client) Sync() error {
|
||||
// Mark the client as syncing.
|
||||
// We will keep syncing until the syncing state changes. Either because
|
||||
// Sync is called or StopSync is called.
|
||||
syncingID := cli.incrementSyncingID()
|
||||
nextBatch := cli.Syncer.NextBatchStorer().LoadNextBatch(cli.UserID)
|
||||
filterID := cli.Syncer.FilterStorer().LoadFilter(cli.UserID)
|
||||
if filterID == "" {
|
||||
filterJSON := cli.Syncer.FilterStorer().GetFilterJSON(cli.UserID)
|
||||
resFilter, err := cli.CreateFilter(filterJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filterID = resFilter.FilterID
|
||||
cli.Syncer.FilterStorer().SaveFilter(cli.UserID, filterID)
|
||||
}
|
||||
|
||||
for {
|
||||
resSync, err := cli.SyncRequest(30000, nextBatch, filterID, false, "")
|
||||
if err != nil {
|
||||
duration, err2 := cli.Syncer.OnFailedSync(resSync, err)
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
time.Sleep(duration)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check that the syncing state hasn't changed
|
||||
// Either because we've stopped syncing or another sync has been started.
|
||||
// We discard the response from our sync.
|
||||
if cli.getSyncingID() != syncingID {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save the token now *before* processing it. This means it's possible
|
||||
// to not process some events, but it means that we won't get constantly stuck processing
|
||||
// a malformed/buggy event which keeps making us panic.
|
||||
cli.Syncer.NextBatchStorer().SaveNextBatch(cli.UserID, resSync.NextBatch)
|
||||
if err = cli.Syncer.ProcessResponse(resSync, nextBatch); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nextBatch = resSync.NextBatch
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) incrementSyncingID() uint32 {
|
||||
cli.syncingMutex.Lock()
|
||||
defer cli.syncingMutex.Unlock()
|
||||
cli.syncingID++
|
||||
return cli.syncingID
|
||||
}
|
||||
|
||||
func (cli *Client) getSyncingID() uint32 {
|
||||
cli.syncingMutex.Lock()
|
||||
defer cli.syncingMutex.Unlock()
|
||||
return cli.syncingID
|
||||
}
|
||||
|
||||
// StopSync stops the ongoing sync started by Sync.
|
||||
func (cli *Client) StopSync() {
|
||||
// Advance the syncing state so that any running Syncs will terminate.
|
||||
cli.incrementSyncingID()
|
||||
}
|
||||
|
||||
// SendJSON sends JSON to the given URL. Returns an error if the response is not 2xx.
|
||||
func (cli *Client) SendJSON(method string, httpURL string, contentJSON interface{}) ([]byte, error) {
|
||||
jsonStr, err := json.Marshal(contentJSON)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequest(method, httpURL, bytes.NewBuffer(jsonStr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
res, err := cli.Client.Do(req)
|
||||
if res != nil {
|
||||
defer res.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contents, err := ioutil.ReadAll(res.Body)
|
||||
if res.StatusCode >= 300 || res.StatusCode < 200 {
|
||||
return nil, HTTPError{
|
||||
Code: res.StatusCode,
|
||||
Message: "Failed to " + method + " JSON: HTTP " + strconv.Itoa(res.StatusCode),
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return contents, nil
|
||||
}
|
||||
|
||||
// CreateFilter makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter
|
||||
func (cli *Client) CreateFilter(filter json.RawMessage) (*RespCreateFilter, error) {
|
||||
urlPath := cli.BuildURL("user", cli.UserID, "filter")
|
||||
resBytes, err := cli.SendJSON("POST", urlPath, &filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var filterResponse RespCreateFilter
|
||||
if err = json.Unmarshal(resBytes, &filterResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &filterResponse, nil
|
||||
}
|
||||
|
||||
// SyncRequest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync
|
||||
func (cli *Client) SyncRequest(timeout int, since, filterID string, fullState bool, setPresence string) (*RespSync, error) {
|
||||
query := map[string]string{
|
||||
"timeout": strconv.Itoa(timeout),
|
||||
}
|
||||
if since != "" {
|
||||
query["since"] = since
|
||||
}
|
||||
if filterID != "" {
|
||||
query["filter"] = filterID
|
||||
}
|
||||
if setPresence != "" {
|
||||
query["set_presence"] = setPresence
|
||||
}
|
||||
if fullState {
|
||||
query["full_state"] = "true"
|
||||
}
|
||||
urlPath := cli.BuildURLWithQuery([]string{"sync"}, query)
|
||||
req, err := http.NewRequest("GET", urlPath, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := cli.Client.Do(req)
|
||||
if res != nil {
|
||||
defer res.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var syncResponse RespSync
|
||||
err = json.NewDecoder(res.Body).Decode(&syncResponse)
|
||||
return &syncResponse, err
|
||||
}
|
||||
|
||||
// NewClient creates a new Matrix Client ready for syncing
|
||||
func NewClient(homeserverURL, userID, accessToken string) (*Client, error) {
|
||||
hsURL, err := url.Parse(homeserverURL)
|
||||
|
|
@ -69,15 +237,21 @@ func NewClient(homeserverURL, userID, accessToken string) (*Client, error) {
|
|||
HomeserverURL: hsURL,
|
||||
UserID: userID,
|
||||
Prefix: "/_matrix/client/r0",
|
||||
Syncer: NewDefaultSyncer(
|
||||
userID,
|
||||
// By default, use an in-memory next_batch storer which will never save tokens to disk.
|
||||
// The client will work with this storer: it just won't
|
||||
// remember the token across restarts. In practice, a database backend should be used.
|
||||
&InMemoryNextBatchStore{make(map[string]string)},
|
||||
// By default, use an in-memory filter storer which will never save the filter ID to disk.
|
||||
// The client will work with this storer: it just won't remember the filter
|
||||
// ID across restarts and hence request a new one. In practice, a database backend should be used.
|
||||
&InMemoryFilterStore{
|
||||
Filter: json.RawMessage(`{"room":{"timeline":{"limit":50}}}`),
|
||||
UserToFilter: make(map[string]string),
|
||||
},
|
||||
),
|
||||
}
|
||||
// By default, use a no-op next_batch storer which will never save tokens and always
|
||||
// "load" the empty string as a token. The client will work with this storer: it just won't
|
||||
// remember the token across restarts. In practice, a database backend should be used.
|
||||
cli.NextBatchStorer = NopNextBatchStore{}
|
||||
// By default, use a no-op filter storer which will never save the filter ID and always
|
||||
// "load" nothing. The client will work with this storer: it just won't remember the filter
|
||||
// ID across restarts and hence request a new one. In practice, a database backend should be used.
|
||||
cli.FilterStorer = NopFilterStore{}
|
||||
// By default, use the default HTTP client.
|
||||
cli.Client = http.DefaultClient
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue