mirror of
https://github.com/matrix-org/dendrite.git
synced 2025-12-16 11:23:11 -06:00
Merge branch 'master' into app-service-reg
This commit is contained in:
commit
a7a16b79f8
|
|
@ -1,21 +1,23 @@
|
||||||
# Code Style
|
# Code Style
|
||||||
|
|
||||||
We follow the standard Go style using gofmt, but with a few extra
|
We follow the standard Go style using goimports, but with a few extra
|
||||||
considerations.
|
considerations.
|
||||||
|
|
||||||
## Linters
|
## Linters
|
||||||
|
|
||||||
We use `gometalinter` to run a number of linters, the exact list can be found
|
We use `golangci-lint` to run a number of linters, the exact list can be found
|
||||||
in [linter.json](linter.json). Some of these are slow and expensive to run, but
|
under linters in [.golangci.yml](.golangci.yml).
|
||||||
a subset can be found in [linter-fast.json](linter-fast.json) that run quickly
|
[Installation](https://github.com/golangci/golangci-lint#install) and [Editor
|
||||||
enough to be run as part of an IDE.
|
Integration](https://github.com/golangci/golangci-lint#editor-integration) for
|
||||||
|
it can be found in the readme of golangci-lint.
|
||||||
|
|
||||||
For rare cases where a linter is giving a spurious warning, it can be disabled
|
For rare cases where a linter is giving a spurious warning, it can be disabled
|
||||||
for that line or statement using a [comment directive](https://github.com/alecthomas/gometalinter#comment-directives), e.g.
|
for that line or statement using a [comment
|
||||||
`// nolint: gocyclo`. This should be used sparingly and only when its clear
|
directive](https://github.com/golangci/golangci-lint#nolint), e.g. `var
|
||||||
that the lint warning is spurious.
|
bad_name int //nolint:golint,unused`. This should be used sparingly and only
|
||||||
|
when its clear that the lint warning is spurious.
|
||||||
|
|
||||||
The linters are vendored, and can be run using [scripts/find-lint.sh](scripts/find-lint.sh)
|
The linters can be run using [scripts/find-lint.sh](scripts/find-lint.sh)
|
||||||
(see file for docs) or as part of a build/test/lint cycle using
|
(see file for docs) or as part of a build/test/lint cycle using
|
||||||
[scripts/build-test-lint.sh](scripts/build-test-lint.sh).
|
[scripts/build-test-lint.sh](scripts/build-test-lint.sh).
|
||||||
|
|
||||||
|
|
@ -82,9 +84,9 @@ sets up linting correctly:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"go.gopath": "${workspaceRoot}:${workspaceRoot}/vendor",
|
"go.lintTool":"golangci-lint",
|
||||||
"go.lintOnSave": "workspace",
|
"go.lintFlags": [
|
||||||
"go.lintTool": "gometalinter",
|
"--fast"
|
||||||
"go.lintFlags": ["--config=linter-fast.json", "--concurrency=5"]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -20,34 +20,40 @@ should pick up any unit test and run it). There are also [scripts](scripts) for
|
||||||
[linting](scripts/find-lint.sh) and doing a [build/test/lint
|
[linting](scripts/find-lint.sh) and doing a [build/test/lint
|
||||||
run](scripts/build-test-lint.sh).
|
run](scripts/build-test-lint.sh).
|
||||||
|
|
||||||
|
As of February 2020, we are deprecating support for Go 1.11 and Go 1.12 and are
|
||||||
|
now targeting Go 1.13 or later. Please ensure that you are using at least Go
|
||||||
|
1.13 when developing for Dendrite - our CI will lint and run tests against this
|
||||||
|
version.
|
||||||
|
|
||||||
## Continuous Integration
|
## Continuous Integration
|
||||||
|
|
||||||
When a Pull Request is submitted, continuous integration jobs are run
|
When a Pull Request is submitted, continuous integration jobs are run
|
||||||
automatically to ensure the code builds and is relatively well-written. The
|
automatically to ensure the code builds and is relatively well-written. The jobs
|
||||||
jobs are run on [Buildkite](https://buildkite.com/matrix-dot-org/dendrite/),
|
are run on [Buildkite](https://buildkite.com/matrix-dot-org/dendrite/), and the
|
||||||
and the Buildkite pipeline configuration can be found in Matrix.org's
|
Buildkite pipeline configuration can be found in Matrix.org's [pipelines
|
||||||
[pipelines repository](https://github.com/matrix-org/pipelines).
|
repository](https://github.com/matrix-org/pipelines).
|
||||||
|
|
||||||
If a job fails, click the "details" button and you should be taken to the job's
|
If a job fails, click the "details" button and you should be taken to the job's
|
||||||
logs.
|
logs.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Scroll down to the failing step and you should see some log output. Scan
|
Scroll down to the failing step and you should see some log output. Scan the
|
||||||
the logs until you find what it's complaining about, fix it, submit a new
|
logs until you find what it's complaining about, fix it, submit a new commit,
|
||||||
commit, then rinse and repeat until CI passes.
|
then rinse and repeat until CI passes.
|
||||||
|
|
||||||
### Running CI Tests Locally
|
### Running CI Tests Locally
|
||||||
|
|
||||||
To save waiting for CI to finish after every commit, it is ideal to run the
|
To save waiting for CI to finish after every commit, it is ideal to run the
|
||||||
checks locally before pushing, fixing errors first. This also saves other
|
checks locally before pushing, fixing errors first. This also saves other people
|
||||||
people time as only so many PRs can be tested at a given time.
|
time as only so many PRs can be tested at a given time.
|
||||||
|
|
||||||
To execute what Buildkite tests, first run `./scripts/build-test-lint.sh`;
|
To execute what Buildkite tests, first run `./scripts/build-test-lint.sh`; this
|
||||||
this script will build the code, lint it, and run `go test ./...` with race
|
script will build the code, lint it, and run `go test ./...` with race condition
|
||||||
condition checking enabled. If something needs to be changed, fix it and then
|
checking enabled. If something needs to be changed, fix it and then run the
|
||||||
run the script again until it no longer complains. Be warned that the linting
|
script again until it no longer complains. Be warned that the linting can take a
|
||||||
can take a significant amount of CPU and RAM.
|
significant amount of CPU and RAM.
|
||||||
|
|
||||||
Once the code builds, run [Sytest](https://github.com/matrix-org/sytest)
|
Once the code builds, run [Sytest](https://github.com/matrix-org/sytest)
|
||||||
according to the guide in
|
according to the guide in
|
||||||
|
|
@ -61,16 +67,18 @@ tests.
|
||||||
|
|
||||||
## Picking Things To Do
|
## Picking Things To Do
|
||||||
|
|
||||||
If you're new then feel free to pick up an issue labelled [good first issue](https://github.com/matrix-org/dendrite/labels/good%20first%20issue).
|
If you're new then feel free to pick up an issue labelled [good first
|
||||||
|
issue](https://github.com/matrix-org/dendrite/labels/good%20first%20issue).
|
||||||
These should be well-contained, small pieces of work that can be picked up to
|
These should be well-contained, small pieces of work that can be picked up to
|
||||||
help you get familiar with the code base.
|
help you get familiar with the code base.
|
||||||
|
|
||||||
Once you're comfortable with hacking on Dendrite there are issues lablled as
|
Once you're comfortable with hacking on Dendrite there are issues lablled as
|
||||||
[help wanted](https://github.com/matrix-org/dendrite/labels/help%20wanted), these
|
[help wanted](https://github.com/matrix-org/dendrite/labels/help%20wanted),
|
||||||
are often slightly larger or more complicated pieces of work but are hopefully
|
these are often slightly larger or more complicated pieces of work but are
|
||||||
nonetheless fairly well-contained.
|
hopefully nonetheless fairly well-contained.
|
||||||
|
|
||||||
We ask people who are familiar with Dendrite to leave the [good first issue](https://github.com/matrix-org/dendrite/labels/good%20first%20issue)
|
We ask people who are familiar with Dendrite to leave the [good first
|
||||||
|
issue](https://github.com/matrix-org/dendrite/labels/good%20first%20issue)
|
||||||
issues so that there is always a way for new people to come and get involved.
|
issues so that there is always a way for new people to come and get involved.
|
||||||
|
|
||||||
## Getting Help
|
## Getting Help
|
||||||
|
|
@ -79,9 +87,11 @@ For questions related to developing on Dendrite we have a dedicated room on
|
||||||
Matrix [#dendrite-dev:matrix.org](https://matrix.to/#/#dendrite-dev:matrix.org)
|
Matrix [#dendrite-dev:matrix.org](https://matrix.to/#/#dendrite-dev:matrix.org)
|
||||||
where we're happy to help.
|
where we're happy to help.
|
||||||
|
|
||||||
For more general questions please use [#dendrite:matrix.org](https://matrix.to/#/#dendrite:matrix.org).
|
For more general questions please use
|
||||||
|
[#dendrite:matrix.org](https://matrix.to/#/#dendrite:matrix.org).
|
||||||
|
|
||||||
## Sign off
|
## Sign off
|
||||||
|
|
||||||
We ask that everyone who contributes to the project signs off their
|
We ask that everyone who contributes to the project signs off their
|
||||||
contributions, in accordance with the [DCO](https://github.com/matrix-org/matrix-doc/blob/master/CONTRIBUTING.rst#sign-off).
|
contributions, in accordance with the
|
||||||
|
[DCO](https://github.com/matrix-org/matrix-doc/blob/master/CONTRIBUTING.rst#sign-off).
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ Dendrite can be run in one of two configurations:
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Go 1.11+
|
- Go 1.13+
|
||||||
- Postgres 9.5+
|
- Postgres 9.5+
|
||||||
- For Kafka (optional if using the monolith server):
|
- For Kafka (optional if using the monolith server):
|
||||||
- Unix-based system (https://kafka.apache.org/documentation/#os)
|
- Unix-based system (https://kafka.apache.org/documentation/#os)
|
||||||
|
|
@ -22,7 +22,7 @@ Dendrite can be run in one of two configurations:
|
||||||
|
|
||||||
## Setting up a development environment
|
## Setting up a development environment
|
||||||
|
|
||||||
Assumes Go 1.10+ and JDK 1.8+ are already installed and are on PATH.
|
Assumes Go 1.13+ and JDK 1.8+ are already installed and are on PATH.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Get the code
|
# Get the code
|
||||||
|
|
@ -101,7 +101,7 @@ Create config file, based on `dendrite-config.yaml`. Call it `dendrite.yaml`. Th
|
||||||
|
|
||||||
It is possible to use 'naffka' as an in-process replacement to Kafka when using
|
It is possible to use 'naffka' as an in-process replacement to Kafka when using
|
||||||
the monolith server. To do this, set `use_naffka: true` in `dendrite.yaml` and uncomment
|
the monolith server. To do this, set `use_naffka: true` in `dendrite.yaml` and uncomment
|
||||||
the necessary line related to naffka in the `database` section. Be sure to update the
|
the necessary line related to naffka in the `database` section. Be sure to update the
|
||||||
database username and password if needed.
|
database username and password if needed.
|
||||||
|
|
||||||
The monolith server can be started as shown below. By default it listens for
|
The monolith server can be started as shown below. By default it listens for
|
||||||
|
|
@ -255,7 +255,7 @@ you want to support federation.
|
||||||
./bin/dendrite-federation-sender-server --config dendrite.yaml
|
./bin/dendrite-federation-sender-server --config dendrite.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run an appservice server
|
### Run an appservice server
|
||||||
|
|
||||||
This sends events from the network to [application
|
This sends events from the network to [application
|
||||||
services](https://matrix.org/docs/spec/application_service/unstable.html)
|
services](https://matrix.org/docs/spec/application_service/unstable.html)
|
||||||
|
|
|
||||||
25
README.md
25
README.md
|
|
@ -1,27 +1,30 @@
|
||||||
# Dendrite [](https://buildkite.com/matrix-dot-org/dendrite) [](https://matrix.to/#/#dendrite-dev:matrix.org) [](https://matrix.to/#/#dendrite:matrix.org)
|
# Dendrite [](https://buildkite.com/matrix-dot-org/dendrite) [](https://matrix.to/#/#dendrite-dev:matrix.org) [](https://matrix.to/#/#dendrite:matrix.org)
|
||||||
|
|
||||||
Dendrite will be a matrix homeserver written in go.
|
Dendrite will be a second-generation Matrix homeserver written in Go.
|
||||||
|
|
||||||
It's still very much a work in progress, but installation instructions can
|
It's still very much a work in progress, but installation instructions can be
|
||||||
be found in [INSTALL.md](INSTALL.md)
|
found in [INSTALL.md](INSTALL.md). It is not recommended to use Dendrite as a
|
||||||
|
production homeserver at this time.
|
||||||
|
|
||||||
An overview of the design can be found in [DESIGN.md](DESIGN.md)
|
An overview of the design can be found in [DESIGN.md](DESIGN.md).
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
Everyone is welcome to help out and contribute! See [CONTRIBUTING.md](CONTRIBUTING.md)
|
Everyone is welcome to help out and contribute! See
|
||||||
to get started!
|
[CONTRIBUTING.md](CONTRIBUTING.md) to get started!
|
||||||
|
|
||||||
We aim to try and make it as easy as possible to jump in.
|
Please note that, as of February 2020, Dendrite now only targets Go 1.13 or
|
||||||
|
later. Please ensure that you are using at least Go 1.13 when developing for
|
||||||
|
Dendrite.
|
||||||
|
|
||||||
# Discussion
|
# Discussion
|
||||||
|
|
||||||
For questions about Dendrite we have a dedicated room on Matrix
|
For questions about Dendrite we have a dedicated room on Matrix
|
||||||
[#dendrite:matrix.org](https://matrix.to/#/#dendrite:matrix.org).
|
[#dendrite:matrix.org](https://matrix.to/#/#dendrite:matrix.org). Development
|
||||||
Development discussion should happen in
|
discussion should happen in
|
||||||
[#dendrite-dev:matrix.org](https://matrix.to/#/#dendrite-dev:matrix.org).
|
[#dendrite-dev:matrix.org](https://matrix.to/#/#dendrite-dev:matrix.org).
|
||||||
|
|
||||||
# Progress
|
# Progress
|
||||||
|
|
||||||
There's plenty still to do to make Dendrite usable! We're tracking progress in
|
There's plenty still to do to make Dendrite usable! We're tracking progress in a
|
||||||
a [project board](https://github.com/matrix-org/dendrite/projects/2).
|
[project board](https://github.com/matrix-org/dendrite/projects/2).
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import (
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const pathPrefixApp = "/_matrix/app/r0"
|
const pathPrefixApp = "/_matrix/app/v1"
|
||||||
|
|
||||||
// Setup registers HTTP handlers with the given ServeMux. It also supplies the given http.Client
|
// Setup registers HTTP handlers with the given ServeMux. It also supplies the given http.Client
|
||||||
// to clients which need to make outbound HTTP requests.
|
// to clients which need to make outbound HTTP requests.
|
||||||
|
|
|
||||||
2
build.sh
2
build.sh
|
|
@ -1,3 +1,3 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
GOBIN=$PWD/`dirname $0`/bin go install -v ./cmd/...
|
GOBIN=$PWD/`dirname $0`/bin go install -v $PWD/`dirname $0`/cmd/...
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,17 @@ func (d *Database) CreateAccount(
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := d.SaveAccountData(ctx, localpart, "", "m.push_rules", `{
|
||||||
|
"global": {
|
||||||
|
"content": [],
|
||||||
|
"override": [],
|
||||||
|
"room": [],
|
||||||
|
"sender": [],
|
||||||
|
"underride": []
|
||||||
|
}
|
||||||
|
}`); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return d.accounts.insertAccount(ctx, localpart, hash, appserviceID)
|
return d.accounts.insertAccount(ctx, localpart, hash, appserviceID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,38 @@ import (
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GetAccountData implements GET /user/{userId}/[rooms/{roomid}/]account_data/{type}
|
||||||
|
func GetAccountData(
|
||||||
|
req *http.Request, accountDB *accounts.Database, device *authtypes.Device,
|
||||||
|
userID string, roomID string, dataType string,
|
||||||
|
) util.JSONResponse {
|
||||||
|
if userID != device.UserID {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: jsonerror.Forbidden("userID does not match the current user"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
|
||||||
|
if err != nil {
|
||||||
|
return httputil.LogThenError(req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if data, err := accountDB.GetAccountDataByType(
|
||||||
|
req.Context(), localpart, roomID, dataType,
|
||||||
|
); err == nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
JSON: jsonerror.Forbidden("data not found"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SaveAccountData implements PUT /user/{userId}/[rooms/{roomId}/]account_data/{type}
|
// SaveAccountData implements PUT /user/{userId}/[rooms/{roomId}/]account_data/{type}
|
||||||
func SaveAccountData(
|
func SaveAccountData(
|
||||||
req *http.Request, accountDB *accounts.Database, device *authtypes.Device,
|
req *http.Request, accountDB *accounts.Database, device *authtypes.Device,
|
||||||
|
|
|
||||||
51
clientapi/routing/capabilities.go
Normal file
51
clientapi/routing/capabilities.go
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package routing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SendMembership implements PUT /rooms/{roomID}/(join|kick|ban|unban|leave|invite)
|
||||||
|
// by building a m.room.member event then sending it to the room server
|
||||||
|
func GetCapabilities(
|
||||||
|
req *http.Request, queryAPI roomserverAPI.RoomserverQueryAPI,
|
||||||
|
) util.JSONResponse {
|
||||||
|
roomVersionsQueryReq := roomserverAPI.QueryRoomVersionCapabilitiesRequest{}
|
||||||
|
var roomVersionsQueryRes roomserverAPI.QueryRoomVersionCapabilitiesResponse
|
||||||
|
if err := queryAPI.QueryRoomVersionCapabilities(
|
||||||
|
req.Context(),
|
||||||
|
&roomVersionsQueryReq,
|
||||||
|
&roomVersionsQueryRes,
|
||||||
|
); err != nil {
|
||||||
|
return httputil.LogThenError(req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"capabilities": map[string]interface{}{
|
||||||
|
"m.room_versions": roomVersionsQueryRes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: response,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -320,7 +320,7 @@ func (r joinRoomReq) joinRoomUsingServer(roomID string, server gomatrixserverlib
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = r.producer.SendEventWithState(
|
if err = r.producer.SendEventWithState(
|
||||||
r.req.Context(), gomatrixserverlib.RespState(respSendJoin), event,
|
r.req.Context(), gomatrixserverlib.RespState(respSendJoin.RespState), event,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
res := httputil.LogThenError(r.req, err)
|
res := httputil.LogThenError(r.req, err)
|
||||||
return &res, nil
|
return &res, nil
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,14 @@ type flow struct {
|
||||||
Stages []string `json:"stages"`
|
Stages []string `json:"stages"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type loginIdentifier struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
User string `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
type passwordRequest struct {
|
type passwordRequest struct {
|
||||||
User string `json:"user"`
|
Identifier loginIdentifier `json:"identifier"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
// Both DeviceID and InitialDisplayName can be omitted, or empty strings ("")
|
// Both DeviceID and InitialDisplayName can be omitted, or empty strings ("")
|
||||||
// Thus a pointer is needed to differentiate between the two
|
// Thus a pointer is needed to differentiate between the two
|
||||||
InitialDisplayName *string `json:"initial_device_display_name"`
|
InitialDisplayName *string `json:"initial_device_display_name"`
|
||||||
|
|
@ -75,34 +80,43 @@ func Login(
|
||||||
}
|
}
|
||||||
} else if req.Method == http.MethodPost {
|
} else if req.Method == http.MethodPost {
|
||||||
var r passwordRequest
|
var r passwordRequest
|
||||||
|
var acc *authtypes.Account
|
||||||
resErr := httputil.UnmarshalJSONRequest(req, &r)
|
resErr := httputil.UnmarshalJSONRequest(req, &r)
|
||||||
if resErr != nil {
|
if resErr != nil {
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
if r.User == "" {
|
switch r.Identifier.Type {
|
||||||
|
case "m.id.user":
|
||||||
|
if r.Identifier.User == "" {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.BadJSON("'user' must be supplied."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util.GetLogger(req.Context()).WithField("user", r.Identifier.User).Info("Processing login request")
|
||||||
|
|
||||||
|
localpart, err := userutil.ParseUsernameParam(r.Identifier.User, &cfg.Matrix.ServerName)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.InvalidUsername(err.Error()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
acc, err = accountDB.GetAccountByPassword(req.Context(), localpart, r.Password)
|
||||||
|
if err != nil {
|
||||||
|
// Technically we could tell them if the user does not exist by checking if err == sql.ErrNoRows
|
||||||
|
// but that would leak the existence of the user.
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: jsonerror.Forbidden("username or password was incorrect, or the account does not exist"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
JSON: jsonerror.BadJSON("'user' must be supplied."),
|
JSON: jsonerror.BadJSON("login identifier '" + r.Identifier.Type + "' not supported"),
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
util.GetLogger(req.Context()).WithField("user", r.User).Info("Processing login request")
|
|
||||||
|
|
||||||
localpart, err := userutil.ParseUsernameParam(r.User, &cfg.Matrix.ServerName)
|
|
||||||
if err != nil {
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
JSON: jsonerror.InvalidUsername(err.Error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
acc, err := accountDB.GetAccountByPassword(req.Context(), localpart, r.Password)
|
|
||||||
if err != nil {
|
|
||||||
// Technically we could tell them if the user does not exist by checking if err == sql.ErrNoRows
|
|
||||||
// but that would leak the existence of the user.
|
|
||||||
return util.JSONResponse{
|
|
||||||
Code: http.StatusForbidden,
|
|
||||||
JSON: jsonerror.Forbidden("username or password was incorrect, or the account does not exist"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/userutil"
|
"github.com/matrix-org/dendrite/clientapi/userutil"
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/tokens"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
@ -449,6 +450,9 @@ func Register(
|
||||||
if resErr != nil {
|
if resErr != nil {
|
||||||
return *resErr
|
return *resErr
|
||||||
}
|
}
|
||||||
|
if req.URL.Query().Get("kind") == "guest" {
|
||||||
|
return handleGuestRegistration(req, r, cfg, accountDB, deviceDB)
|
||||||
|
}
|
||||||
|
|
||||||
// Retrieve or generate the sessionID
|
// Retrieve or generate the sessionID
|
||||||
sessionID := r.Auth.Session
|
sessionID := r.Auth.Session
|
||||||
|
|
@ -505,6 +509,59 @@ func Register(
|
||||||
return handleRegistrationFlow(req, r, sessionID, cfg, accountDB, deviceDB)
|
return handleRegistrationFlow(req, r, sessionID, cfg, accountDB, deviceDB)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleGuestRegistration(
|
||||||
|
req *http.Request,
|
||||||
|
r registerRequest,
|
||||||
|
cfg *config.Dendrite,
|
||||||
|
accountDB *accounts.Database,
|
||||||
|
deviceDB *devices.Database,
|
||||||
|
) util.JSONResponse {
|
||||||
|
|
||||||
|
//Generate numeric local part for guest user
|
||||||
|
id, err := accountDB.GetNewNumericLocalpart(req.Context())
|
||||||
|
if err != nil {
|
||||||
|
return httputil.LogThenError(req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
localpart := strconv.FormatInt(id, 10)
|
||||||
|
acc, err := accountDB.CreateAccount(req.Context(), localpart, "", "")
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: jsonerror.Unknown("failed to create account: " + err.Error()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
token, err := tokens.GenerateLoginToken(tokens.TokenOptions{
|
||||||
|
ServerPrivateKey: cfg.Matrix.PrivateKey.Seed(),
|
||||||
|
ServerName: string(acc.ServerName),
|
||||||
|
UserID: acc.UserID,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: jsonerror.Unknown("Failed to generate access token"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//we don't allow guests to specify their own device_id
|
||||||
|
dev, err := deviceDB.CreateDevice(req.Context(), acc.Localpart, nil, token, r.InitialDisplayName)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: jsonerror.Unknown("failed to create device: " + err.Error()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: registerResponse{
|
||||||
|
UserID: dev.UserID,
|
||||||
|
AccessToken: dev.AccessToken,
|
||||||
|
HomeServer: acc.ServerName,
|
||||||
|
DeviceID: dev.ID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// handleRegistrationFlow will direct and complete registration flow stages
|
// handleRegistrationFlow will direct and complete registration flow stages
|
||||||
// that the client has requested.
|
// that the client has requested.
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
|
|
|
||||||
|
|
@ -430,6 +430,26 @@ func Setup(
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPut, http.MethodOptions)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
|
||||||
|
r0mux.Handle("/user/{userID}/account_data/{type}",
|
||||||
|
common.MakeAuthAPI("user_account_data", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||||
|
vars, err := common.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
if err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
return GetAccountData(req, accountDB, device, vars["userID"], "", vars["type"])
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodGet)
|
||||||
|
|
||||||
|
r0mux.Handle("/user/{userID}/rooms/{roomID}/account_data/{type}",
|
||||||
|
common.MakeAuthAPI("user_account_data", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||||
|
vars, err := common.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
if err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
return GetAccountData(req, accountDB, device, vars["userID"], vars["roomID"], vars["type"])
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodGet)
|
||||||
|
|
||||||
r0mux.Handle("/rooms/{roomID}/members",
|
r0mux.Handle("/rooms/{roomID}/members",
|
||||||
common.MakeAuthAPI("rooms_members", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
common.MakeAuthAPI("rooms_members", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||||
vars, err := common.URLDecodeMapValues(mux.Vars(req))
|
vars, err := common.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
@ -531,4 +551,10 @@ func Setup(
|
||||||
return DeleteTag(req, accountDB, device, vars["userId"], vars["roomId"], vars["tag"], syncProducer)
|
return DeleteTag(req, accountDB, device, vars["userId"], vars["roomId"], vars["tag"], syncProducer)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodDelete, http.MethodOptions)
|
).Methods(http.MethodDelete, http.MethodOptions)
|
||||||
|
|
||||||
|
r0mux.Handle("/capabilities",
|
||||||
|
common.MakeAuthAPI("capabilities", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||||
|
return GetCapabilities(req, queryAPI)
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodGet)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ func main() {
|
||||||
deviceDB := base.CreateDeviceDB()
|
deviceDB := base.CreateDeviceDB()
|
||||||
keyDB := base.CreateKeyDB()
|
keyDB := base.CreateKeyDB()
|
||||||
federation := base.CreateFederationClient()
|
federation := base.CreateFederationClient()
|
||||||
|
federationSender := base.CreateHTTPFederationSenderAPIs()
|
||||||
keyRing := keydb.CreateKeyRing(federation.Client, keyDB)
|
keyRing := keydb.CreateKeyRing(federation.Client, keyDB)
|
||||||
|
|
||||||
alias, input, query := base.CreateHTTPRoomserverAPIs()
|
alias, input, query := base.CreateHTTPRoomserverAPIs()
|
||||||
|
|
@ -36,7 +37,7 @@ func main() {
|
||||||
|
|
||||||
federationapi.SetupFederationAPIComponent(
|
federationapi.SetupFederationAPIComponent(
|
||||||
base, accountDB, deviceDB, federation, &keyRing,
|
base, accountDB, deviceDB, federation, &keyRing,
|
||||||
alias, input, query, asQuery,
|
alias, input, query, asQuery, federationSender,
|
||||||
)
|
)
|
||||||
|
|
||||||
base.SetupAndServeHTTP(string(base.Cfg.Bind.FederationAPI), string(base.Cfg.Listen.FederationAPI))
|
base.SetupAndServeHTTP(string(base.Cfg.Bind.FederationAPI), string(base.Cfg.Listen.FederationAPI))
|
||||||
|
|
|
||||||
|
|
@ -67,10 +67,10 @@ func main() {
|
||||||
federation, &keyRing, alias, input, query,
|
federation, &keyRing, alias, input, query,
|
||||||
typingInputAPI, asQuery, transactions.New(), fedSenderAPI,
|
typingInputAPI, asQuery, transactions.New(), fedSenderAPI,
|
||||||
)
|
)
|
||||||
federationapi.SetupFederationAPIComponent(base, accountDB, deviceDB, federation, &keyRing, alias, input, query, asQuery)
|
federationapi.SetupFederationAPIComponent(base, accountDB, deviceDB, federation, &keyRing, alias, input, query, asQuery, fedSenderAPI)
|
||||||
mediaapi.SetupMediaAPIComponent(base, deviceDB)
|
mediaapi.SetupMediaAPIComponent(base, deviceDB)
|
||||||
publicroomsapi.SetupPublicRoomsAPIComponent(base, deviceDB)
|
publicroomsapi.SetupPublicRoomsAPIComponent(base, deviceDB, query)
|
||||||
syncapi.SetupSyncAPIComponent(base, deviceDB, accountDB, query)
|
syncapi.SetupSyncAPIComponent(base, deviceDB, accountDB, query, federation, cfg)
|
||||||
|
|
||||||
httpHandler := common.WrapHandlerInCORS(base.APIMux)
|
httpHandler := common.WrapHandlerInCORS(base.APIMux)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,9 @@ func main() {
|
||||||
|
|
||||||
deviceDB := base.CreateDeviceDB()
|
deviceDB := base.CreateDeviceDB()
|
||||||
|
|
||||||
publicroomsapi.SetupPublicRoomsAPIComponent(base, deviceDB)
|
_, _, query := base.CreateHTTPRoomserverAPIs()
|
||||||
|
|
||||||
|
publicroomsapi.SetupPublicRoomsAPIComponent(base, deviceDB, query)
|
||||||
|
|
||||||
base.SetupAndServeHTTP(string(base.Cfg.Bind.PublicRoomsAPI), string(base.Cfg.Listen.PublicRoomsAPI))
|
base.SetupAndServeHTTP(string(base.Cfg.Bind.PublicRoomsAPI), string(base.Cfg.Listen.PublicRoomsAPI))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,11 @@ func main() {
|
||||||
|
|
||||||
deviceDB := base.CreateDeviceDB()
|
deviceDB := base.CreateDeviceDB()
|
||||||
accountDB := base.CreateAccountsDB()
|
accountDB := base.CreateAccountsDB()
|
||||||
|
federation := base.CreateFederationClient()
|
||||||
|
|
||||||
_, _, query := base.CreateHTTPRoomserverAPIs()
|
_, _, query := base.CreateHTTPRoomserverAPIs()
|
||||||
|
|
||||||
syncapi.SetupSyncAPIComponent(base, deviceDB, accountDB, query)
|
syncapi.SetupSyncAPIComponent(base, deviceDB, accountDB, query, federation, cfg)
|
||||||
|
|
||||||
base.SetupAndServeHTTP(string(base.Cfg.Bind.SyncAPI), string(base.Cfg.Listen.SyncAPI))
|
base.SetupAndServeHTTP(string(base.Cfg.Bind.SyncAPI), string(base.Cfg.Listen.SyncAPI))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/common/keydb"
|
"github.com/matrix-org/dendrite/common/keydb"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/naffka"
|
"github.com/matrix-org/naffka"
|
||||||
|
|
@ -138,8 +140,13 @@ func (b *BaseDendrite) CreateAccountsDB() *accounts.Database {
|
||||||
|
|
||||||
// CreateKeyDB creates a new instance of the key database. Should only be called
|
// CreateKeyDB creates a new instance of the key database. Should only be called
|
||||||
// once per component.
|
// once per component.
|
||||||
func (b *BaseDendrite) CreateKeyDB() *keydb.Database {
|
func (b *BaseDendrite) CreateKeyDB() keydb.Database {
|
||||||
db, err := keydb.NewDatabase(string(b.Cfg.Database.ServerKey))
|
db, err := keydb.NewDatabase(
|
||||||
|
string(b.Cfg.Database.ServerKey),
|
||||||
|
b.Cfg.Matrix.ServerName,
|
||||||
|
b.Cfg.Matrix.PrivateKey.Public().(ed25519.PublicKey),
|
||||||
|
b.Cfg.Matrix.KeyID,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Panicf("failed to connect to keys db")
|
logrus.WithError(err).Panicf("failed to connect to keys db")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
opentracing "github.com/opentracing/opentracing-go"
|
opentracing "github.com/opentracing/opentracing-go"
|
||||||
"github.com/opentracing/opentracing-go/ext"
|
"github.com/opentracing/opentracing-go/ext"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -59,7 +60,16 @@ func MakeHTMLAPI(metricsName string, f func(http.ResponseWriter, *http.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return prometheus.InstrumentHandler(metricsName, http.HandlerFunc(withSpan))
|
return promhttp.InstrumentHandlerCounter(
|
||||||
|
promauto.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: metricsName,
|
||||||
|
Help: "Total number of http requests for HTML resources",
|
||||||
|
},
|
||||||
|
[]string{"code"},
|
||||||
|
),
|
||||||
|
http.HandlerFunc(withSpan),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeInternalAPI turns a util.JSONRequestHandler function into an http.Handler.
|
// MakeInternalAPI turns a util.JSONRequestHandler function into an http.Handler.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -16,67 +16,35 @@ package keydb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"net/url"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/common/keydb/postgres"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A Database implements gomatrixserverlib.KeyDatabase and is used to store
|
type Database interface {
|
||||||
// the public keys for other matrix servers.
|
FetcherName() string
|
||||||
type Database struct {
|
FetchKeys(ctx context.Context, requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error)
|
||||||
statements serverKeyStatements
|
StoreKeys(ctx context.Context, keyMap map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDatabase prepares a new key database.
|
// NewDatabase opens a database connection.
|
||||||
// It creates the necessary tables if they don't already exist.
|
func NewDatabase(
|
||||||
// It prepares all the SQL statements that it will use.
|
dataSourceName string,
|
||||||
// Returns an error if there was a problem talking to the database.
|
serverName gomatrixserverlib.ServerName,
|
||||||
func NewDatabase(dataSourceName string) (*Database, error) {
|
serverKey ed25519.PublicKey,
|
||||||
db, err := sql.Open("postgres", dataSourceName)
|
serverKeyID gomatrixserverlib.KeyID,
|
||||||
|
) (Database, error) {
|
||||||
|
uri, err := url.Parse(dataSourceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return postgres.NewDatabase(dataSourceName, serverName, serverKey, serverKeyID)
|
||||||
}
|
}
|
||||||
d := &Database{}
|
switch uri.Scheme {
|
||||||
err = d.statements.prepare(db)
|
case "postgres":
|
||||||
if err != nil {
|
return postgres.NewDatabase(dataSourceName, serverName, serverKey, serverKeyID)
|
||||||
return nil, err
|
default:
|
||||||
|
return postgres.NewDatabase(dataSourceName, serverName, serverKey, serverKeyID)
|
||||||
}
|
}
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetcherName implements KeyFetcher
|
|
||||||
func (d Database) FetcherName() string {
|
|
||||||
return "KeyDatabase"
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetchKeys implements gomatrixserverlib.KeyDatabase
|
|
||||||
func (d *Database) FetchKeys(
|
|
||||||
ctx context.Context,
|
|
||||||
requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp,
|
|
||||||
) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) {
|
|
||||||
return d.statements.bulkSelectServerKeys(ctx, requests)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreKeys implements gomatrixserverlib.KeyDatabase
|
|
||||||
func (d *Database) StoreKeys(
|
|
||||||
ctx context.Context,
|
|
||||||
keyMap map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult,
|
|
||||||
) error {
|
|
||||||
// TODO: Inserting all the keys within a single transaction may
|
|
||||||
// be more efficient since the transaction overhead can be quite
|
|
||||||
// high for a single insert statement.
|
|
||||||
var lastErr error
|
|
||||||
for request, keys := range keyMap {
|
|
||||||
if err := d.statements.upsertServerKeys(ctx, request, keys); err != nil {
|
|
||||||
// Rather than returning immediately on error we try to insert the
|
|
||||||
// remaining keys.
|
|
||||||
// Since we are inserting the keys outside of a transaction it is
|
|
||||||
// possible for some of the inserts to succeed even though some
|
|
||||||
// of the inserts have failed.
|
|
||||||
// Ensuring that we always insert all the keys we can means that
|
|
||||||
// this behaviour won't depend on the iteration order of the map.
|
|
||||||
lastErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lastErr
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
113
common/keydb/postgres/keydb.go
Normal file
113
common/keydb/postgres/keydb.go
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Database implements gomatrixserverlib.KeyDatabase and is used to store
|
||||||
|
// the public keys for other matrix servers.
|
||||||
|
type Database struct {
|
||||||
|
statements serverKeyStatements
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDatabase prepares a new key database.
|
||||||
|
// It creates the necessary tables if they don't already exist.
|
||||||
|
// It prepares all the SQL statements that it will use.
|
||||||
|
// Returns an error if there was a problem talking to the database.
|
||||||
|
func NewDatabase(
|
||||||
|
dataSourceName string,
|
||||||
|
serverName gomatrixserverlib.ServerName,
|
||||||
|
serverKey ed25519.PublicKey,
|
||||||
|
serverKeyID gomatrixserverlib.KeyID,
|
||||||
|
) (*Database, error) {
|
||||||
|
db, err := sql.Open("postgres", dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d := &Database{}
|
||||||
|
err = d.statements.prepare(db)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Store our own keys so that we don't end up making HTTP requests to find our
|
||||||
|
// own keys
|
||||||
|
index := gomatrixserverlib.PublicKeyLookupRequest{
|
||||||
|
ServerName: serverName,
|
||||||
|
KeyID: serverKeyID,
|
||||||
|
}
|
||||||
|
value := gomatrixserverlib.PublicKeyLookupResult{
|
||||||
|
VerifyKey: gomatrixserverlib.VerifyKey{
|
||||||
|
Key: gomatrixserverlib.Base64String(serverKey),
|
||||||
|
},
|
||||||
|
ValidUntilTS: math.MaxUint64 >> 1,
|
||||||
|
ExpiredTS: gomatrixserverlib.PublicKeyNotExpired,
|
||||||
|
}
|
||||||
|
err = d.StoreKeys(
|
||||||
|
context.Background(),
|
||||||
|
map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult{
|
||||||
|
index: value,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetcherName implements KeyFetcher
|
||||||
|
func (d Database) FetcherName() string {
|
||||||
|
return "KeyDatabase"
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchKeys implements gomatrixserverlib.KeyDatabase
|
||||||
|
func (d *Database) FetchKeys(
|
||||||
|
ctx context.Context,
|
||||||
|
requests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp,
|
||||||
|
) (map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, error) {
|
||||||
|
return d.statements.bulkSelectServerKeys(ctx, requests)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreKeys implements gomatrixserverlib.KeyDatabase
|
||||||
|
func (d *Database) StoreKeys(
|
||||||
|
ctx context.Context,
|
||||||
|
keyMap map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult,
|
||||||
|
) error {
|
||||||
|
// TODO: Inserting all the keys within a single transaction may
|
||||||
|
// be more efficient since the transaction overhead can be quite
|
||||||
|
// high for a single insert statement.
|
||||||
|
var lastErr error
|
||||||
|
for request, keys := range keyMap {
|
||||||
|
if err := d.statements.upsertServerKeys(ctx, request, keys); err != nil {
|
||||||
|
// Rather than returning immediately on error we try to insert the
|
||||||
|
// remaining keys.
|
||||||
|
// Since we are inserting the keys outside of a transaction it is
|
||||||
|
// possible for some of the inserts to succeed even though some
|
||||||
|
// of the inserts have failed.
|
||||||
|
// Ensuring that we always insert all the keys we can means that
|
||||||
|
// this behaviour won't depend on the iteration order of the map.
|
||||||
|
lastErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lastErr
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package keydb
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -85,7 +85,7 @@ func (t *Cache) AddTransaction(accessToken, txnID string, res *util.JSONResponse
|
||||||
// It guarantees that an entry will be present in cache for at least cleanupPeriod & at most 2 * cleanupPeriod.
|
// It guarantees that an entry will be present in cache for at least cleanupPeriod & at most 2 * cleanupPeriod.
|
||||||
// This cycles the txnMaps forward, i.e. back map is assigned the front and front is assigned an empty map.
|
// This cycles the txnMaps forward, i.e. back map is assigned the front and front is assigned an empty map.
|
||||||
func cacheCleanService(t *Cache) {
|
func cacheCleanService(t *Cache) {
|
||||||
ticker := time.Tick(t.cleanupPeriod)
|
ticker := time.NewTicker(t.cleanupPeriod).C
|
||||||
for range ticker {
|
for range ticker {
|
||||||
t.Lock()
|
t.Lock()
|
||||||
t.txnsMaps[1] = t.txnsMaps[0]
|
t.txnsMaps[1] = t.txnsMaps[0]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
FROM docker.io/golang:1.12.5-alpine3.9
|
FROM docker.io/golang:1.13.6-alpine
|
||||||
|
|
||||||
RUN mkdir /build
|
RUN mkdir /build
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,4 @@
|
||||||
|
|
||||||
bash ./docker/build.sh
|
bash ./docker/build.sh
|
||||||
|
|
||||||
./bin/dendrite-monolith-server --tls-cert=server.crt --tls-key=server.key
|
./bin/dendrite-monolith-server --tls-cert=server.crt --tls-key=server.key $@
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,17 @@
|
||||||
|
|
||||||
Dendrite uses [SyTest](https://github.com/matrix-org/sytest) for its
|
Dendrite uses [SyTest](https://github.com/matrix-org/sytest) for its
|
||||||
integration testing. When creating a new PR, add the test IDs (see below) that
|
integration testing. When creating a new PR, add the test IDs (see below) that
|
||||||
your PR should allow to pass to `testfile` in dendrite's root directory. Not all
|
your PR should allow to pass to `sytest-whitelist` in dendrite's root
|
||||||
PRs need to make new tests pass. If we find your PR should be making a test pass
|
directory. Not all PRs need to make new tests pass. If we find your PR should
|
||||||
we may ask you to add to that file, as generally Dendrite's progress can be
|
be making a test pass we may ask you to add to that file, as generally
|
||||||
tracked through the amount of SyTest tests it passes.
|
Dendrite's progress can be tracked through the amount of SyTest tests it
|
||||||
|
passes.
|
||||||
|
|
||||||
## Finding out which tests to add
|
## Finding out which tests to add
|
||||||
|
|
||||||
We recommend you run the tests locally by manually setting up SyTest or using a
|
We recommend you run the tests locally by manually setting up SyTest or using a
|
||||||
SyTest docker image. After running the tests, a script will print the tests you
|
SyTest docker image. After running the tests, a script will print the tests you
|
||||||
need to add to `testfile` for you.
|
need to add to `sytest-whitelist`.
|
||||||
|
|
||||||
You should proceed after you see no build problems for dendrite after running:
|
You should proceed after you see no build problems for dendrite after running:
|
||||||
|
|
||||||
|
|
@ -50,16 +51,16 @@ EOF
|
||||||
Run the tests:
|
Run the tests:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./run-tests.pl -I Dendrite::Monolith -d ../dendrite/bin -W ../dendrite/testfile -O tap --all | tee results.tap
|
./run-tests.pl -I Dendrite::Monolith -d ../dendrite/bin -W ../dendrite/sytest-whitelist -O tap --all | tee results.tap
|
||||||
```
|
```
|
||||||
|
|
||||||
where `tee` lets you see the results while they're being piped to the file.
|
where `tee` lets you see the results while they're being piped to the file.
|
||||||
|
|
||||||
Once the tests are complete, run the helper script to see if you need to add
|
Once the tests are complete, run the helper script to see if you need to add
|
||||||
any newly passing test names to `testfile` in the project's root directory:
|
any newly passing test names to `sytest-whitelist` in the project's root directory:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
../dendrite/show-expected-fail-tests.sh results.tap ../dendrite/testfile
|
../dendrite/show-expected-fail-tests.sh results.tap ../dendrite/sytest-whitelist ../dendrite/sytest-blacklist
|
||||||
```
|
```
|
||||||
|
|
||||||
If the script prints nothing/exits with 0, then you're good to go.
|
If the script prints nothing/exits with 0, then you're good to go.
|
||||||
|
|
@ -75,4 +76,4 @@ docker run --rm -v /path/to/dendrite/:/src/ matrixdotorg/sytest-dendrite
|
||||||
|
|
||||||
where `/path/to/dendrite/` should be replaced with the actual path to your
|
where `/path/to/dendrite/` should be replaced with the actual path to your
|
||||||
dendrite source code. The output should tell you if you need to add any tests to
|
dendrite source code. The output should tell you if you need to add any tests to
|
||||||
`testfile`.
|
`sytest-whitelist`.
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
||||||
"github.com/matrix-org/dendrite/common/basecomponent"
|
"github.com/matrix-org/dendrite/common/basecomponent"
|
||||||
|
federationSenderAPI "github.com/matrix-org/dendrite/federationsender/api"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
|
||||||
// TODO: Are we really wanting to pull in the producer from clientapi
|
// TODO: Are we really wanting to pull in the producer from clientapi
|
||||||
|
|
@ -39,11 +40,13 @@ func SetupFederationAPIComponent(
|
||||||
inputAPI roomserverAPI.RoomserverInputAPI,
|
inputAPI roomserverAPI.RoomserverInputAPI,
|
||||||
queryAPI roomserverAPI.RoomserverQueryAPI,
|
queryAPI roomserverAPI.RoomserverQueryAPI,
|
||||||
asAPI appserviceAPI.AppServiceQueryAPI,
|
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||||
|
federationSenderAPI federationSenderAPI.FederationSenderQueryAPI,
|
||||||
) {
|
) {
|
||||||
roomserverProducer := producers.NewRoomserverProducer(inputAPI)
|
roomserverProducer := producers.NewRoomserverProducer(inputAPI)
|
||||||
|
|
||||||
routing.Setup(
|
routing.Setup(
|
||||||
base.APIMux, *base.Cfg, queryAPI, aliasAPI, asAPI,
|
base.APIMux, *base.Cfg, queryAPI, aliasAPI, asAPI,
|
||||||
roomserverProducer, *keyRing, federation, accountsDB, deviceDB,
|
roomserverProducer, federationSenderAPI, *keyRing, federation, accountsDB,
|
||||||
|
deviceDB,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,11 @@ package routing
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
"github.com/matrix-org/dendrite/federationapi/types"
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
@ -90,9 +90,11 @@ func Backfill(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
txn := types.NewTransaction()
|
txn := gomatrixserverlib.Transaction{
|
||||||
txn.Origin = cfg.Matrix.ServerName
|
Origin: cfg.Matrix.ServerName,
|
||||||
txn.PDUs = evs
|
PDUs: evs,
|
||||||
|
OriginServerTS: gomatrixserverlib.AsTimestamp(time.Now()),
|
||||||
|
}
|
||||||
|
|
||||||
// Send the events to the client.
|
// Send the events to the client.
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
|
|
|
||||||
|
|
@ -154,16 +154,23 @@ func SendJoin(
|
||||||
|
|
||||||
// Fetch the state and auth chain. We do this before we send the events
|
// Fetch the state and auth chain. We do this before we send the events
|
||||||
// on, in case this fails.
|
// on, in case this fails.
|
||||||
var stateAndAuthChainRepsonse api.QueryStateAndAuthChainResponse
|
var stateAndAuthChainResponse api.QueryStateAndAuthChainResponse
|
||||||
err = query.QueryStateAndAuthChain(httpReq.Context(), &api.QueryStateAndAuthChainRequest{
|
err = query.QueryStateAndAuthChain(httpReq.Context(), &api.QueryStateAndAuthChainRequest{
|
||||||
PrevEventIDs: event.PrevEventIDs(),
|
PrevEventIDs: event.PrevEventIDs(),
|
||||||
AuthEventIDs: event.AuthEventIDs(),
|
AuthEventIDs: event.AuthEventIDs(),
|
||||||
RoomID: roomID,
|
RoomID: roomID,
|
||||||
}, &stateAndAuthChainRepsonse)
|
}, &stateAndAuthChainResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.LogThenError(httpReq, err)
|
return httputil.LogThenError(httpReq, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !stateAndAuthChainResponse.RoomExists {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
JSON: jsonerror.NotFound("Room does not exist"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Send the events to the room server.
|
// Send the events to the room server.
|
||||||
// We are responsible for notifying other servers that the user has joined
|
// We are responsible for notifying other servers that the user has joined
|
||||||
// the room, so set SendAsServer to cfg.Matrix.ServerName
|
// the room, so set SendAsServer to cfg.Matrix.ServerName
|
||||||
|
|
@ -177,8 +184,8 @@ func SendJoin(
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
JSON: map[string]interface{}{
|
JSON: map[string]interface{}{
|
||||||
"state": stateAndAuthChainRepsonse.StateEvents,
|
"state": stateAndAuthChainResponse.StateEvents,
|
||||||
"auth_chain": stateAndAuthChainRepsonse.AuthChainEvents,
|
"auth_chain": stateAndAuthChainResponse.AuthChainEvents,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/httputil"
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
|
federationSenderAPI "github.com/matrix-org/dendrite/federationsender/api"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/gomatrix"
|
"github.com/matrix-org/gomatrix"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
@ -33,6 +34,7 @@ func RoomAliasToID(
|
||||||
federation *gomatrixserverlib.FederationClient,
|
federation *gomatrixserverlib.FederationClient,
|
||||||
cfg config.Dendrite,
|
cfg config.Dendrite,
|
||||||
aliasAPI roomserverAPI.RoomserverAliasAPI,
|
aliasAPI roomserverAPI.RoomserverAliasAPI,
|
||||||
|
senderAPI federationSenderAPI.FederationSenderQueryAPI,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
roomAlias := httpReq.FormValue("room_alias")
|
roomAlias := httpReq.FormValue("room_alias")
|
||||||
if roomAlias == "" {
|
if roomAlias == "" {
|
||||||
|
|
@ -59,10 +61,15 @@ func RoomAliasToID(
|
||||||
}
|
}
|
||||||
|
|
||||||
if queryRes.RoomID != "" {
|
if queryRes.RoomID != "" {
|
||||||
// TODO: List servers that are aware of this room alias
|
serverQueryReq := federationSenderAPI.QueryJoinedHostServerNamesInRoomRequest{RoomID: queryRes.RoomID}
|
||||||
|
var serverQueryRes federationSenderAPI.QueryJoinedHostServerNamesInRoomResponse
|
||||||
|
if err = senderAPI.QueryJoinedHostServerNamesInRoom(httpReq.Context(), &serverQueryReq, &serverQueryRes); err != nil {
|
||||||
|
return httputil.LogThenError(httpReq, err)
|
||||||
|
}
|
||||||
|
|
||||||
resp = gomatrixserverlib.RespDirectory{
|
resp = gomatrixserverlib.RespDirectory{
|
||||||
RoomID: queryRes.RoomID,
|
RoomID: queryRes.RoomID,
|
||||||
Servers: []gomatrixserverlib.ServerName{},
|
Servers: serverQueryRes.ServerNames,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If no alias was found, return an error
|
// If no alias was found, return an error
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/producers"
|
"github.com/matrix-org/dendrite/clientapi/producers"
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
|
federationSenderAPI "github.com/matrix-org/dendrite/federationsender/api"
|
||||||
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
|
@ -32,6 +33,7 @@ import (
|
||||||
const (
|
const (
|
||||||
pathPrefixV2Keys = "/_matrix/key/v2"
|
pathPrefixV2Keys = "/_matrix/key/v2"
|
||||||
pathPrefixV1Federation = "/_matrix/federation/v1"
|
pathPrefixV1Federation = "/_matrix/federation/v1"
|
||||||
|
pathPrefixV2Federation = "/_matrix/federation/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Setup registers HTTP handlers with the given ServeMux.
|
// Setup registers HTTP handlers with the given ServeMux.
|
||||||
|
|
@ -46,6 +48,7 @@ func Setup(
|
||||||
aliasAPI roomserverAPI.RoomserverAliasAPI,
|
aliasAPI roomserverAPI.RoomserverAliasAPI,
|
||||||
asAPI appserviceAPI.AppServiceQueryAPI,
|
asAPI appserviceAPI.AppServiceQueryAPI,
|
||||||
producer *producers.RoomserverProducer,
|
producer *producers.RoomserverProducer,
|
||||||
|
federationSenderAPI federationSenderAPI.FederationSenderQueryAPI,
|
||||||
keys gomatrixserverlib.KeyRing,
|
keys gomatrixserverlib.KeyRing,
|
||||||
federation *gomatrixserverlib.FederationClient,
|
federation *gomatrixserverlib.FederationClient,
|
||||||
accountDB *accounts.Database,
|
accountDB *accounts.Database,
|
||||||
|
|
@ -53,6 +56,7 @@ func Setup(
|
||||||
) {
|
) {
|
||||||
v2keysmux := apiMux.PathPrefix(pathPrefixV2Keys).Subrouter()
|
v2keysmux := apiMux.PathPrefix(pathPrefixV2Keys).Subrouter()
|
||||||
v1fedmux := apiMux.PathPrefix(pathPrefixV1Federation).Subrouter()
|
v1fedmux := apiMux.PathPrefix(pathPrefixV1Federation).Subrouter()
|
||||||
|
v2fedmux := apiMux.PathPrefix(pathPrefixV2Federation).Subrouter()
|
||||||
|
|
||||||
localKeys := common.MakeExternalAPI("localkeys", func(req *http.Request) util.JSONResponse {
|
localKeys := common.MakeExternalAPI("localkeys", func(req *http.Request) util.JSONResponse {
|
||||||
return LocalKeys(cfg)
|
return LocalKeys(cfg)
|
||||||
|
|
@ -156,7 +160,7 @@ func Setup(
|
||||||
"federation_query_room_alias", cfg.Matrix.ServerName, keys,
|
"federation_query_room_alias", cfg.Matrix.ServerName, keys,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||||
return RoomAliasToID(
|
return RoomAliasToID(
|
||||||
httpReq, federation, cfg, aliasAPI,
|
httpReq, federation, cfg, aliasAPI, federationSenderAPI,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)).Methods(http.MethodGet)
|
)).Methods(http.MethodGet)
|
||||||
|
|
@ -198,7 +202,7 @@ func Setup(
|
||||||
},
|
},
|
||||||
)).Methods(http.MethodGet)
|
)).Methods(http.MethodGet)
|
||||||
|
|
||||||
v1fedmux.Handle("/send_join/{roomID}/{userID}", common.MakeFedAPI(
|
v2fedmux.Handle("/send_join/{roomID}/{userID}", common.MakeFedAPI(
|
||||||
"federation_send_join", cfg.Matrix.ServerName, keys,
|
"federation_send_join", cfg.Matrix.ServerName, keys,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||||
vars, err := common.URLDecodeMapValues(mux.Vars(httpReq))
|
vars, err := common.URLDecodeMapValues(mux.Vars(httpReq))
|
||||||
|
|
@ -228,7 +232,7 @@ func Setup(
|
||||||
},
|
},
|
||||||
)).Methods(http.MethodGet)
|
)).Methods(http.MethodGet)
|
||||||
|
|
||||||
v1fedmux.Handle("/send_leave/{roomID}/{userID}", common.MakeFedAPI(
|
v2fedmux.Handle("/send_leave/{roomID}/{userID}", common.MakeFedAPI(
|
||||||
"federation_send_leave", cfg.Matrix.ServerName, keys,
|
"federation_send_leave", cfg.Matrix.ServerName, keys,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||||
vars, err := common.URLDecodeMapValues(mux.Vars(httpReq))
|
vars, err := common.URLDecodeMapValues(mux.Vars(httpReq))
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
// Copyright 2018 New Vector Ltd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Transaction is the representation of a transaction from the federation API
|
|
||||||
// See https://matrix.org/docs/spec/server_server/unstable.html for more info.
|
|
||||||
type Transaction struct {
|
|
||||||
// The server_name of the homeserver sending this transaction.
|
|
||||||
Origin gomatrixserverlib.ServerName `json:"origin"`
|
|
||||||
// POSIX timestamp in milliseconds on originating homeserver when this
|
|
||||||
// transaction started.
|
|
||||||
OriginServerTS int64 `json:"origin_server_ts"`
|
|
||||||
// List of persistent updates to rooms.
|
|
||||||
PDUs []gomatrixserverlib.Event `json:"pdus"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTransaction sets the timestamp of a new transaction instance and then
|
|
||||||
// returns the said instance.
|
|
||||||
func NewTransaction() Transaction {
|
|
||||||
// Retrieve the current timestamp in nanoseconds and make it a milliseconds
|
|
||||||
// one.
|
|
||||||
ts := time.Now().UnixNano() / int64(time.Millisecond)
|
|
||||||
|
|
||||||
return Transaction{OriginServerTS: ts}
|
|
||||||
}
|
|
||||||
|
|
@ -33,7 +33,7 @@ import (
|
||||||
// OutputRoomEventConsumer consumes events that originated in the room server.
|
// OutputRoomEventConsumer consumes events that originated in the room server.
|
||||||
type OutputRoomEventConsumer struct {
|
type OutputRoomEventConsumer struct {
|
||||||
roomServerConsumer *common.ContinualConsumer
|
roomServerConsumer *common.ContinualConsumer
|
||||||
db *storage.Database
|
db storage.Database
|
||||||
queues *queue.OutgoingQueues
|
queues *queue.OutgoingQueues
|
||||||
query api.RoomserverQueryAPI
|
query api.RoomserverQueryAPI
|
||||||
}
|
}
|
||||||
|
|
@ -43,7 +43,7 @@ func NewOutputRoomEventConsumer(
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
kafkaConsumer sarama.Consumer,
|
kafkaConsumer sarama.Consumer,
|
||||||
queues *queue.OutgoingQueues,
|
queues *queue.OutgoingQueues,
|
||||||
store *storage.Database,
|
store storage.Database,
|
||||||
queryAPI api.RoomserverQueryAPI,
|
queryAPI api.RoomserverQueryAPI,
|
||||||
) *OutputRoomEventConsumer {
|
) *OutputRoomEventConsumer {
|
||||||
consumer := common.ContinualConsumer{
|
consumer := common.ContinualConsumer{
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import (
|
||||||
// OutputTypingEventConsumer consumes events that originate in typing server.
|
// OutputTypingEventConsumer consumes events that originate in typing server.
|
||||||
type OutputTypingEventConsumer struct {
|
type OutputTypingEventConsumer struct {
|
||||||
consumer *common.ContinualConsumer
|
consumer *common.ContinualConsumer
|
||||||
db *storage.Database
|
db storage.Database
|
||||||
queues *queue.OutgoingQueues
|
queues *queue.OutgoingQueues
|
||||||
ServerName gomatrixserverlib.ServerName
|
ServerName gomatrixserverlib.ServerName
|
||||||
}
|
}
|
||||||
|
|
@ -39,7 +39,7 @@ func NewOutputTypingEventConsumer(
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
kafkaConsumer sarama.Consumer,
|
kafkaConsumer sarama.Consumer,
|
||||||
queues *queue.OutgoingQueues,
|
queues *queue.OutgoingQueues,
|
||||||
store *storage.Database,
|
store storage.Database,
|
||||||
) *OutputTypingEventConsumer {
|
) *OutputTypingEventConsumer {
|
||||||
consumer := common.ContinualConsumer{
|
consumer := common.ContinualConsumer{
|
||||||
Topic: string(cfg.Kafka.Topics.OutputTypingEvent),
|
Topic: string(cfg.Kafka.Topics.OutputTypingEvent),
|
||||||
|
|
|
||||||
|
|
@ -45,15 +45,12 @@ func (f *FederationSenderQueryAPI) QueryJoinedHostServerNamesInRoom(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
serverNamesSet := make(map[gomatrixserverlib.ServerName]bool, len(joinedHosts))
|
response.ServerNames = make([]gomatrixserverlib.ServerName, 0, len(joinedHosts))
|
||||||
for _, host := range joinedHosts {
|
for _, host := range joinedHosts {
|
||||||
serverNamesSet[host.ServerName] = true
|
response.ServerNames = append(response.ServerNames, host.ServerName)
|
||||||
}
|
}
|
||||||
|
|
||||||
response.ServerNames = make([]gomatrixserverlib.ServerName, 0, len(serverNamesSet))
|
// TODO: remove duplicates?
|
||||||
for name := range serverNamesSet {
|
|
||||||
response.ServerNames = append(response.ServerNames, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
122
federationsender/storage/postgres/storage.go
Normal file
122
federationsender/storage/postgres/storage.go
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/dendrite/federationsender/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database stores information needed by the federation sender
|
||||||
|
type Database struct {
|
||||||
|
joinedHostsStatements
|
||||||
|
roomStatements
|
||||||
|
common.PartitionOffsetStatements
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDatabase opens a new database
|
||||||
|
func NewDatabase(dataSourceName string) (*Database, error) {
|
||||||
|
var result Database
|
||||||
|
var err error
|
||||||
|
if result.db, err = sql.Open("postgres", dataSourceName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = result.prepare(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) prepare() error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if err = d.joinedHostsStatements.prepare(d.db); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = d.roomStatements.prepare(d.db); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.PartitionOffsetStatements.Prepare(d.db, "federationsender")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRoom updates the joined hosts for a room and returns what the joined
|
||||||
|
// hosts were before the update, or nil if this was a duplicate message.
|
||||||
|
// This is called when we receive a message from kafka, so we pass in
|
||||||
|
// oldEventID and newEventID to check that we haven't missed any messages or
|
||||||
|
// this isn't a duplicate message.
|
||||||
|
func (d *Database) UpdateRoom(
|
||||||
|
ctx context.Context,
|
||||||
|
roomID, oldEventID, newEventID string,
|
||||||
|
addHosts []types.JoinedHost,
|
||||||
|
removeHosts []string,
|
||||||
|
) (joinedHosts []types.JoinedHost, err error) {
|
||||||
|
err = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
||||||
|
err = d.insertRoom(ctx, txn, roomID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lastSentEventID, err := d.selectRoomForUpdate(ctx, txn, roomID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastSentEventID == newEventID {
|
||||||
|
// We've handled this message before, so let's just ignore it.
|
||||||
|
// We can only get a duplicate for the last message we processed,
|
||||||
|
// so its enough just to compare the newEventID with lastSentEventID
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastSentEventID != oldEventID {
|
||||||
|
return types.EventIDMismatchError{
|
||||||
|
DatabaseID: lastSentEventID, RoomServerID: oldEventID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
joinedHosts, err = d.selectJoinedHostsWithTx(ctx, txn, roomID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, add := range addHosts {
|
||||||
|
err = d.insertJoinedHosts(ctx, txn, roomID, add.MemberEventID, add.ServerName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = d.deleteJoinedHosts(ctx, txn, removeHosts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return d.updateRoom(ctx, txn, roomID, newEventID)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetJoinedHosts returns the currently joined hosts for room,
|
||||||
|
// as known to federationserver.
|
||||||
|
// Returns an error if something goes wrong.
|
||||||
|
func (d *Database) GetJoinedHosts(
|
||||||
|
ctx context.Context, roomID string,
|
||||||
|
) ([]types.JoinedHost, error) {
|
||||||
|
return d.selectJoinedHosts(ctx, roomID)
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -16,106 +16,29 @@ package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"net/url"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/dendrite/federationsender/storage/postgres"
|
||||||
"github.com/matrix-org/dendrite/federationsender/types"
|
"github.com/matrix-org/dendrite/federationsender/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Database stores information needed by the federation sender
|
type Database interface {
|
||||||
type Database struct {
|
common.PartitionStorer
|
||||||
joinedHostsStatements
|
UpdateRoom(ctx context.Context, roomID, oldEventID, newEventID string, addHosts []types.JoinedHost, removeHosts []string) (joinedHosts []types.JoinedHost, err error)
|
||||||
roomStatements
|
GetJoinedHosts(ctx context.Context, roomID string) ([]types.JoinedHost, error)
|
||||||
common.PartitionOffsetStatements
|
|
||||||
db *sql.DB
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDatabase opens a new database
|
// NewDatabase opens a new database
|
||||||
func NewDatabase(dataSourceName string) (*Database, error) {
|
func NewDatabase(dataSourceName string) (Database, error) {
|
||||||
var result Database
|
uri, err := url.Parse(dataSourceName)
|
||||||
var err error
|
if err != nil {
|
||||||
if result.db, err = sql.Open("postgres", dataSourceName); err != nil {
|
return postgres.NewDatabase(dataSourceName)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
if err = result.prepare(); err != nil {
|
switch uri.Scheme {
|
||||||
return nil, err
|
case "postgres":
|
||||||
|
return postgres.NewDatabase(dataSourceName)
|
||||||
|
default:
|
||||||
|
return postgres.NewDatabase(dataSourceName)
|
||||||
}
|
}
|
||||||
return &result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Database) prepare() error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = d.joinedHostsStatements.prepare(d.db); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = d.roomStatements.prepare(d.db); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.PartitionOffsetStatements.Prepare(d.db, "federationsender")
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateRoom updates the joined hosts for a room and returns what the joined
|
|
||||||
// hosts were before the update, or nil if this was a duplicate message.
|
|
||||||
// This is called when we receive a message from kafka, so we pass in
|
|
||||||
// oldEventID and newEventID to check that we haven't missed any messages or
|
|
||||||
// this isn't a duplicate message.
|
|
||||||
func (d *Database) UpdateRoom(
|
|
||||||
ctx context.Context,
|
|
||||||
roomID, oldEventID, newEventID string,
|
|
||||||
addHosts []types.JoinedHost,
|
|
||||||
removeHosts []string,
|
|
||||||
) (joinedHosts []types.JoinedHost, err error) {
|
|
||||||
err = common.WithTransaction(d.db, func(txn *sql.Tx) error {
|
|
||||||
err = d.insertRoom(ctx, txn, roomID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
lastSentEventID, err := d.selectRoomForUpdate(ctx, txn, roomID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if lastSentEventID == newEventID {
|
|
||||||
// We've handled this message before, so let's just ignore it.
|
|
||||||
// We can only get a duplicate for the last message we processed,
|
|
||||||
// so its enough just to compare the newEventID with lastSentEventID
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if lastSentEventID != oldEventID {
|
|
||||||
return types.EventIDMismatchError{
|
|
||||||
DatabaseID: lastSentEventID, RoomServerID: oldEventID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
joinedHosts, err = d.selectJoinedHostsWithTx(ctx, txn, roomID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, add := range addHosts {
|
|
||||||
err = d.insertJoinedHosts(ctx, txn, roomID, add.MemberEventID, add.ServerName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = d.deleteJoinedHosts(ctx, txn, removeHosts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return d.updateRoom(ctx, txn, roomID, newEventID)
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetJoinedHosts returns the currently joined hosts for room,
|
|
||||||
// as known to federationserver.
|
|
||||||
// Returns an error if something goes wrong.
|
|
||||||
func (d *Database) GetJoinedHosts(
|
|
||||||
ctx context.Context, roomID string,
|
|
||||||
) ([]types.JoinedHost, error) {
|
|
||||||
return d.selectJoinedHosts(ctx, roomID)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
76
go.mod
76
go.mod
|
|
@ -2,66 +2,44 @@ module github.com/matrix-org/dendrite
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Shopify/sarama v0.0.0-20170127151855-574d3147eee3
|
github.com/Shopify/sarama v0.0.0-20170127151855-574d3147eee3
|
||||||
github.com/alecthomas/gometalinter v2.0.2+incompatible
|
github.com/Shopify/toxiproxy v2.1.4+incompatible // indirect
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf
|
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect
|
||||||
github.com/apache/thrift v0.0.0-20161221203622-b2a4d4ae21c7
|
github.com/eapache/go-resiliency v0.0.0-20160104191539-b86b1ec0dd42 // indirect
|
||||||
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a
|
github.com/eapache/go-xerial-snappy v0.0.0-20160609142408-bb955e01b934 // indirect
|
||||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd
|
github.com/eapache/queue v1.1.0 // indirect
|
||||||
github.com/crossdock/crossdock-go v0.0.0-20160816171116-049aabb0122b
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1
|
|
||||||
github.com/eapache/go-resiliency v0.0.0-20160104191539-b86b1ec0dd42
|
|
||||||
github.com/eapache/go-xerial-snappy v0.0.0-20160609142408-bb955e01b934
|
|
||||||
github.com/eapache/queue v1.1.0
|
|
||||||
github.com/golang/protobuf v0.0.0-20161117033126-8ee79997227b
|
|
||||||
github.com/golang/snappy v0.0.0-20170119014723-7db9049039a0
|
|
||||||
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9
|
|
||||||
github.com/gorilla/context v1.1.1
|
|
||||||
github.com/gorilla/mux v1.7.3
|
github.com/gorilla/mux v1.7.3
|
||||||
github.com/jaegertracing/jaeger-client-go v0.0.0-20170921145708-3ad49a1d839b
|
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6 // indirect
|
||||||
github.com/jaegertracing/jaeger-lib v0.0.0-20170920222118-21a3da6d66fe
|
|
||||||
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||||
github.com/lib/pq v0.0.0-20170918175043-23da1db4f16d
|
github.com/lib/pq v1.2.0
|
||||||
github.com/matrix-org/dugong v0.0.0-20171220115018-ea0a4690a0d5
|
github.com/matrix-org/dugong v0.0.0-20171220115018-ea0a4690a0d5
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26
|
github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20190814163046-d6285a18401f
|
github.com/matrix-org/gomatrixserverlib v0.0.0-20200124100636-0c2ec91d1df5
|
||||||
github.com/matrix-org/naffka v0.0.0-20171115094957-662bfd0841d0
|
github.com/matrix-org/naffka v0.0.0-20171115094957-662bfd0841d0
|
||||||
github.com/matrix-org/util v0.0.0-20171127121716-2e2df66af2f5
|
github.com/matrix-org/util v0.0.0-20171127121716-2e2df66af2f5
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1
|
github.com/miekg/dns v1.1.12 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5
|
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5
|
||||||
github.com/nicksnyder/go-i18n v1.8.1
|
github.com/opentracing/opentracing-go v1.0.2
|
||||||
github.com/opentracing/opentracing-go v0.0.0-20170806192116-8ebe5d4e236e
|
github.com/pierrec/lz4 v0.0.0-20161206202305-5c9560bfa9ac // indirect
|
||||||
github.com/pelletier/go-toml v0.0.0-20170904195809-1d6b12b7cb29
|
github.com/pierrec/xxHash v0.0.0-20160112165351-5a004441f897 // indirect
|
||||||
github.com/pierrec/lz4 v0.0.0-20161206202305-5c9560bfa9ac
|
github.com/pkg/errors v0.8.1
|
||||||
github.com/pierrec/xxHash v0.0.0-20160112165351-5a004441f897
|
github.com/prometheus/client_golang v1.2.1
|
||||||
github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17
|
github.com/rcrowley/go-metrics v0.0.0-20161128210544-1f30fe9094a5 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0
|
|
||||||
github.com/prometheus/client_golang v0.0.0-20180519192340-c51dc758d4bb
|
|
||||||
github.com/prometheus/client_model v0.0.0-20150212101744-fa8ad6fec335
|
|
||||||
github.com/prometheus/common v0.0.0-20170108231212-dd2f054febf4
|
|
||||||
github.com/prometheus/procfs v0.0.0-20170128160123-1878d9fbb537
|
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20161128210544-1f30fe9094a5
|
|
||||||
github.com/sirupsen/logrus v1.4.2
|
github.com/sirupsen/logrus v1.4.2
|
||||||
github.com/stretchr/objx v0.2.0 // indirect
|
github.com/stretchr/testify v1.4.0 // indirect
|
||||||
github.com/stretchr/testify v1.3.0
|
github.com/uber-go/atomic v1.3.0 // indirect
|
||||||
github.com/tidwall/gjson v1.1.5
|
|
||||||
github.com/tidwall/match v1.0.1
|
|
||||||
github.com/tidwall/sjson v1.0.3
|
|
||||||
github.com/uber-go/atomic v1.3.0
|
|
||||||
github.com/uber/jaeger-client-go v2.15.0+incompatible
|
github.com/uber/jaeger-client-go v2.15.0+incompatible
|
||||||
github.com/uber/jaeger-lib v1.5.0
|
github.com/uber/jaeger-lib v1.5.0
|
||||||
github.com/uber/tchannel-go v0.0.0-20170927010734-b3e26487e291
|
go.uber.org/atomic v1.3.0 // indirect
|
||||||
go.uber.org/atomic v1.3.0
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
|
||||||
go.uber.org/multierr v0.0.0-20170829224307-fb7d312c2c04
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect
|
||||||
go.uber.org/zap v1.7.1
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613
|
|
||||||
golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95
|
|
||||||
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7
|
|
||||||
gopkg.in/Shopify/sarama.v1 v1.11.0
|
gopkg.in/Shopify/sarama.v1 v1.11.0
|
||||||
gopkg.in/airbrake/gobrake.v2 v2.0.9
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||||
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20170727041045-23bcc3c4eae3
|
|
||||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2
|
|
||||||
gopkg.in/h2non/bimg.v1 v1.0.18
|
gopkg.in/h2non/bimg.v1 v1.0.18
|
||||||
gopkg.in/macaroon.v2 v2.1.0
|
|
||||||
gopkg.in/yaml.v2 v2.2.2
|
gopkg.in/yaml.v2 v2.2.2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
|
||||||
174
go.sum
174
go.sum
|
|
@ -1,13 +1,19 @@
|
||||||
github.com/Shopify/sarama v0.0.0-20170127151855-574d3147eee3 h1:j6BAEHYn1kUyW2j7kY0mOJ/R8A0qWwXpvUAEHGemm/g=
|
github.com/Shopify/sarama v0.0.0-20170127151855-574d3147eee3 h1:j6BAEHYn1kUyW2j7kY0mOJ/R8A0qWwXpvUAEHGemm/g=
|
||||||
github.com/Shopify/sarama v0.0.0-20170127151855-574d3147eee3/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
github.com/Shopify/sarama v0.0.0-20170127151855-574d3147eee3/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||||
github.com/alecthomas/gometalinter v2.0.2+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk=
|
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
|
||||||
|
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||||
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/apache/thrift v0.0.0-20161221203622-b2a4d4ae21c7/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a h1:BtpsbiV638WQZwhA98cEZw2BsbnQJrbd0BI7tsy0W1c=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
|
||||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w=
|
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w=
|
||||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||||
github.com/crossdock/crossdock-go v0.0.0-20160816171116-049aabb0122b/go.mod h1:v9FBN7gdVTpiD/+LZ7Po0UKvROyT87uLVxTHVky/dlQ=
|
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
|
@ -19,141 +25,165 @@ github.com/eapache/go-xerial-snappy v0.0.0-20160609142408-bb955e01b934/go.mod h1
|
||||||
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||||
github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k=
|
github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k=
|
||||||
github.com/golang/protobuf v0.0.0-20161117033126-8ee79997227b h1:fE/yi9pibxGEc0gSJuEShcsBXE2d5FW3OudsjE9tKzQ=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/golang/protobuf v0.0.0-20161117033126-8ee79997227b/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/golang/snappy v0.0.0-20170119014723-7db9049039a0 h1:FMElzTwkd/2jQ2QzLEzt97JRgvFhYhnYiaQSwZ7tuyU=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/golang/snappy v0.0.0-20170119014723-7db9049039a0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
|
||||||
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
|
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/gorilla/mux v1.3.0 h1:HwSEKGN6U5T2aAQTfu5pW8fiwjSp3IgwdRbkICydk/c=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/gorilla/mux v1.3.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
|
||||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||||
github.com/jaegertracing/jaeger-client-go v0.0.0-20170921145708-3ad49a1d839b/go.mod h1:HWG7INeOG1ZE17I/S8eeb+svquXmBS/hf1Obi6hJUyQ=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/jaegertracing/jaeger-lib v0.0.0-20170920222118-21a3da6d66fe/go.mod h1:VqeqQrZmZr9G4WdLw4ei9tAHU54iJRkfoFHvTTQn4jQ=
|
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6 h1:KAZ1BW2TCmT6PRihDPpocIy1QTtsAsrx6TneU/4+CMg=
|
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6 h1:KAZ1BW2TCmT6PRihDPpocIy1QTtsAsrx6TneU/4+CMg=
|
||||||
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
|
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/lib/pq v0.0.0-20170918175043-23da1db4f16d h1:Hdtccv31GWxWoCzWsIhZXy5NxEktzAkA8lywhTKu8O4=
|
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||||
github.com/lib/pq v0.0.0-20170918175043-23da1db4f16d/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/matrix-org/dugong v0.0.0-20171220115018-ea0a4690a0d5 h1:nMX2t7hbGF0NYDYySx0pCqEKGKAeZIiSqlWSspetlhY=
|
github.com/matrix-org/dugong v0.0.0-20171220115018-ea0a4690a0d5 h1:nMX2t7hbGF0NYDYySx0pCqEKGKAeZIiSqlWSspetlhY=
|
||||||
github.com/matrix-org/dugong v0.0.0-20171220115018-ea0a4690a0d5/go.mod h1:NgPCr+UavRGH6n5jmdX8DuqFZ4JiCWIJoZiuhTRLSUg=
|
github.com/matrix-org/dugong v0.0.0-20171220115018-ea0a4690a0d5/go.mod h1:NgPCr+UavRGH6n5jmdX8DuqFZ4JiCWIJoZiuhTRLSUg=
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20171003113848-a7fc80c8060c h1:aZap604NyBGhAUE0CyNHz6+Pryye5A5mHnYyO4KPPW8=
|
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20171003113848-a7fc80c8060c/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0=
|
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20190130130140-385f072fe9af h1:piaIBNQGIHnni27xRB7VKkEwoWCgAmeuYf8pxAyG0bI=
|
github.com/matrix-org/gomatrix v0.0.0-20190130130140-385f072fe9af h1:piaIBNQGIHnni27xRB7VKkEwoWCgAmeuYf8pxAyG0bI=
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20190130130140-385f072fe9af/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0=
|
github.com/matrix-org/gomatrix v0.0.0-20190130130140-385f072fe9af/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0=
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26 h1:Hr3zjRsq2bhrnp3Ky1qgx/fzCtCALOoGYylh2tpS9K4=
|
github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26 h1:Hr3zjRsq2bhrnp3Ky1qgx/fzCtCALOoGYylh2tpS9K4=
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0=
|
github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0=
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20181109104322-1c2cbc0872f0 h1:3UzhmERBbis4ZaB3imEbZwtDjGz/oVRC2cLLEajCzJA=
|
github.com/matrix-org/gomatrixserverlib v0.0.0-20200124100636-0c2ec91d1df5 h1:kmRjpmFOenVpOaV/DRlo9p6z/IbOKlUC+hhKsAAh8Qg=
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20181109104322-1c2cbc0872f0/go.mod h1:YHyhIQUmuXyKtoVfDUMk/DyU93Taamlu6nPZkij/JtA=
|
github.com/matrix-org/gomatrixserverlib v0.0.0-20200124100636-0c2ec91d1df5/go.mod h1:FsKa2pWE/bpQql9H7U4boOPXFoJX/QcqaZZ6ijLkaZI=
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20190619132215-178ed5e3b8e2 h1:pYajAEdi3sowj4iSunqctchhcMNW3rDjeeH0T4uDkMY=
|
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20190619132215-178ed5e3b8e2/go.mod h1:sf0RcKOdiwJeTti7A313xsaejNUGYDq02MQZ4JD4w/E=
|
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20190724145009-a6df10ef35d6 h1:B8n1H5Wb1B5jwLzTylBpY0kJCMRqrofT7PmOw4aJFJA=
|
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20190724145009-a6df10ef35d6/go.mod h1:sf0RcKOdiwJeTti7A313xsaejNUGYDq02MQZ4JD4w/E=
|
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20190805173246-3a2199d5ecd6 h1:xr69Hk6QM3RIN6JSvx3RpDowBGpHpDDqhqXCeySwYow=
|
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20190805173246-3a2199d5ecd6/go.mod h1:sf0RcKOdiwJeTti7A313xsaejNUGYDq02MQZ4JD4w/E=
|
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20190814163046-d6285a18401f h1:20CZL7ApB7xgR7sZF9yD/qpsP51Sfx0TTgUJ3vKgnZQ=
|
|
||||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20190814163046-d6285a18401f/go.mod h1:sf0RcKOdiwJeTti7A313xsaejNUGYDq02MQZ4JD4w/E=
|
|
||||||
github.com/matrix-org/naffka v0.0.0-20171115094957-662bfd0841d0 h1:p7WTwG+aXM86+yVrYAiCMW3ZHSmotVvuRbjtt3jC+4A=
|
github.com/matrix-org/naffka v0.0.0-20171115094957-662bfd0841d0 h1:p7WTwG+aXM86+yVrYAiCMW3ZHSmotVvuRbjtt3jC+4A=
|
||||||
github.com/matrix-org/naffka v0.0.0-20171115094957-662bfd0841d0/go.mod h1:cXoYQIENbdWIQHt1SyCo6Bl3C3raHwJ0wgVrXHSqf+A=
|
github.com/matrix-org/naffka v0.0.0-20171115094957-662bfd0841d0/go.mod h1:cXoYQIENbdWIQHt1SyCo6Bl3C3raHwJ0wgVrXHSqf+A=
|
||||||
github.com/matrix-org/util v0.0.0-20171013132526-8b1c8ab81986 h1:TiWl4hLvezAhRPM8tPcPDFTysZ7k4T/1J4GPp/iqlZo=
|
|
||||||
github.com/matrix-org/util v0.0.0-20171013132526-8b1c8ab81986/go.mod h1:lePuOiXLNDott7NZfnQvJk0lAZ5HgvIuWGhel6J+RLA=
|
|
||||||
github.com/matrix-org/util v0.0.0-20171127121716-2e2df66af2f5 h1:W7l5CP4V7wPyPb4tYE11dbmeAOwtFQBTW0rf4OonOS8=
|
github.com/matrix-org/util v0.0.0-20171127121716-2e2df66af2f5 h1:W7l5CP4V7wPyPb4tYE11dbmeAOwtFQBTW0rf4OonOS8=
|
||||||
github.com/matrix-org/util v0.0.0-20171127121716-2e2df66af2f5/go.mod h1:lePuOiXLNDott7NZfnQvJk0lAZ5HgvIuWGhel6J+RLA=
|
github.com/matrix-org/util v0.0.0-20171127121716-2e2df66af2f5/go.mod h1:lePuOiXLNDott7NZfnQvJk0lAZ5HgvIuWGhel6J+RLA=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/miekg/dns v1.1.4 h1:rCMZsU2ScVSYcAsOXgmC6+AKOK+6pmQTOcw03nfwYV0=
|
||||||
github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
|
github.com/miekg/dns v1.1.12 h1:WMhc1ik4LNkTg8U9l3hI1LvxKmIL+f1+WV/SZtCbDDA=
|
||||||
|
github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY=
|
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY=
|
||||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/nicksnyder/go-i18n v1.8.1/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q=
|
github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg=
|
||||||
github.com/opentracing/opentracing-go v0.0.0-20170806192116-8ebe5d4e236e h1:4cOVGAdR+woaUwhk6bgWI9ESJQDTaJMr8U4OJlT3J0Q=
|
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
github.com/opentracing/opentracing-go v0.0.0-20170806192116-8ebe5d4e236e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
|
||||||
github.com/pelletier/go-toml v0.0.0-20170904195809-1d6b12b7cb29/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
|
||||||
github.com/pierrec/lz4 v0.0.0-20161206202305-5c9560bfa9ac h1:tKcxwAA5OHUQjL6sWsuCIcP9OnzN+RwKfvomtIOsfy8=
|
github.com/pierrec/lz4 v0.0.0-20161206202305-5c9560bfa9ac h1:tKcxwAA5OHUQjL6sWsuCIcP9OnzN+RwKfvomtIOsfy8=
|
||||||
github.com/pierrec/lz4 v0.0.0-20161206202305-5c9560bfa9ac/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
github.com/pierrec/lz4 v0.0.0-20161206202305-5c9560bfa9ac/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
github.com/pierrec/xxHash v0.0.0-20160112165351-5a004441f897 h1:jp3jc/PyyTrTKjJJ6rWnhTbmo7tGgBFyG9AL5FIrO1I=
|
github.com/pierrec/xxHash v0.0.0-20160112165351-5a004441f897 h1:jp3jc/PyyTrTKjJJ6rWnhTbmo7tGgBFyG9AL5FIrO1I=
|
||||||
github.com/pierrec/xxHash v0.0.0-20160112165351-5a004441f897/go.mod h1:w2waW5Zoa/Wc4Yqe0wgrIYAGKqRMf7czn2HNKXmuL+I=
|
github.com/pierrec/xxHash v0.0.0-20160112165351-5a004441f897/go.mod h1:w2waW5Zoa/Wc4Yqe0wgrIYAGKqRMf7czn2HNKXmuL+I=
|
||||||
github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17 h1:chPfVn+gpAM5CTpTyVU9j8J+xgRGwmoDlNDLjKnJiYo=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v0.0.0-20180519192340-c51dc758d4bb h1:ghXIh3jvLRo/h3y2O7wBgcmH1th5NXQ4XHwK5CgI//I=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v0.0.0-20180519192340-c51dc758d4bb/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_model v0.0.0-20150212101744-fa8ad6fec335 h1:0E/5GnGmzoDCtmzTycjGDWW33H0UBmAhR0h+FC8hWLs=
|
github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI=
|
||||||
github.com/prometheus/client_model v0.0.0-20150212101744-fa8ad6fec335/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
|
||||||
github.com/prometheus/common v0.0.0-20170108231212-dd2f054febf4 h1:bZG2YNnM/Fjd3kiqaVt13Apkhzz24wBKlxQ+URiggXk=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/common v0.0.0-20170108231212-dd2f054febf4/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/procfs v0.0.0-20170128160123-1878d9fbb537 h1:Lq69k27tHOmljEqDOHDy3b6kQyEie2yWeAiF0OKFMJ8=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
||||||
github.com/prometheus/procfs v0.0.0-20170128160123-1878d9fbb537/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
|
||||||
|
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
|
||||||
|
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20161128210544-1f30fe9094a5 h1:gwcdIpH6NU2iF8CmcqD+CP6+1CkRBOhHaPR+iu6raBY=
|
github.com/rcrowley/go-metrics v0.0.0-20161128210544-1f30fe9094a5 h1:gwcdIpH6NU2iF8CmcqD+CP6+1CkRBOhHaPR+iu6raBY=
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20161128210544-1f30fe9094a5/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
github.com/rcrowley/go-metrics v0.0.0-20161128210544-1f30fe9094a5/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
github.com/sirupsen/logrus v0.0.0-20170822132746-89742aefa4b2 h1:+8J/sCAVv2Y9Ct1BKszDFJEVWv6Aynr2O4FYGUg6+Mc=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v0.0.0-20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
|
||||||
github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME=
|
github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME=
|
||||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
|
||||||
github.com/stretchr/testify v0.0.0-20170809224252-890a5c3458b4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/tidwall/gjson v1.0.2 h1:5BsM7kyEAHAUGEGDkEKO9Mdyiuw6QQ6TSDdarP0Nnmk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/tidwall/gjson v1.0.2/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/tidwall/gjson v1.1.5 h1:QysILxBeUEY3GTLA0fQVgkQG1zme8NxGvhh2SSqWNwI=
|
github.com/tidwall/gjson v1.1.5 h1:QysILxBeUEY3GTLA0fQVgkQG1zme8NxGvhh2SSqWNwI=
|
||||||
github.com/tidwall/gjson v1.1.5/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA=
|
github.com/tidwall/gjson v1.1.5/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA=
|
||||||
github.com/tidwall/match v0.0.0-20171002075945-1731857f09b1 h1:pWIN9LOlFRCJFqWIOEbHLvY0WWJddsjH2FQ6N0HKZdU=
|
|
||||||
github.com/tidwall/match v0.0.0-20171002075945-1731857f09b1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
|
||||||
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
|
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
|
||||||
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
||||||
github.com/tidwall/sjson v1.0.0 h1:hOrzQPtGKlKAudQVmU43GkxEgG8TOgKyiKUyb7sE0rs=
|
|
||||||
github.com/tidwall/sjson v1.0.0/go.mod h1:bURseu1nuBkFpIES5cz6zBtjmYeOQmEESshn7VpF15Y=
|
|
||||||
github.com/tidwall/sjson v1.0.3 h1:DeF+0LZqvIt4fKYw41aPB29ZGlvwVkHKktoXJ1YW9Y8=
|
github.com/tidwall/sjson v1.0.3 h1:DeF+0LZqvIt4fKYw41aPB29ZGlvwVkHKktoXJ1YW9Y8=
|
||||||
github.com/tidwall/sjson v1.0.3/go.mod h1:bURseu1nuBkFpIES5cz6zBtjmYeOQmEESshn7VpF15Y=
|
github.com/tidwall/sjson v1.0.3/go.mod h1:bURseu1nuBkFpIES5cz6zBtjmYeOQmEESshn7VpF15Y=
|
||||||
|
github.com/uber-go/atomic v1.3.0 h1:ylWoWcs+jXihgo3Us1Sdsatf2R6+OlBGm8fexR3oFG4=
|
||||||
github.com/uber-go/atomic v1.3.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
|
github.com/uber-go/atomic v1.3.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
|
||||||
github.com/uber/jaeger-client-go v2.15.0+incompatible h1:NP3qsSqNxh8VYr956ur1N/1C1PjvOJnJykCzcD5QHbk=
|
github.com/uber/jaeger-client-go v2.15.0+incompatible h1:NP3qsSqNxh8VYr956ur1N/1C1PjvOJnJykCzcD5QHbk=
|
||||||
github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||||
github.com/uber/jaeger-lib v1.5.0 h1:OHbgr8l656Ub3Fw5k9SWnBfIEwvoHQ+W2y+Aa9D1Uyo=
|
github.com/uber/jaeger-lib v1.5.0 h1:OHbgr8l656Ub3Fw5k9SWnBfIEwvoHQ+W2y+Aa9D1Uyo=
|
||||||
github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
||||||
github.com/uber/tchannel-go v0.0.0-20170927010734-b3e26487e291/go.mod h1:Rrgz1eL8kMjW/nEzZos0t+Heq0O4LhnUJVA32OvWKHo=
|
go.uber.org/atomic v1.3.0 h1:vs7fgriifsPbGdK3bNuMWapNn3qnZhCRXc19NRdq010=
|
||||||
go.uber.org/atomic v1.3.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.3.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/multierr v0.0.0-20170829224307-fb7d312c2c04/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
|
||||||
go.uber.org/zap v1.7.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
|
||||||
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd h1:VtIkGDhk0ph3t+THbvXHfMZ8QHgsBO39Nh52+74pq7w=
|
|
||||||
golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc=
|
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc=
|
||||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/net v0.0.0-20170927055102-0a9397675ba3 h1:tTDpczhDVjW6WN3DinzKcw5juwkDTVn22I7MNlfxSXM=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||||
golang.org/x/net v0.0.0-20170927055102-0a9397675ba3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95 h1:fY7Dsw114eJN4boqzVSbpVHO6rTdhq6/GnXeu+PKnzU=
|
golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95 h1:fY7Dsw114eJN4boqzVSbpVHO6rTdhq6/GnXeu+PKnzU=
|
||||||
golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/sys v0.0.0-20171012164349-43eea11bc926 h1:PY6OU86NqbyZiOzaPnDw6oOjAGtYQqIua16z6y9QkwE=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/sys v0.0.0-20171012164349-43eea11bc926/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
|
||||||
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI=
|
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
|
||||||
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
gopkg.in/Shopify/sarama.v1 v1.11.0 h1:/3kaCyeYaPbr59IBjeqhIcUOB1vXlIVqXAYa5g5C5F0=
|
gopkg.in/Shopify/sarama.v1 v1.11.0 h1:/3kaCyeYaPbr59IBjeqhIcUOB1vXlIVqXAYa5g5C5F0=
|
||||||
gopkg.in/Shopify/sarama.v1 v1.11.0/go.mod h1:AxnvoaevB2nBjNK17cG61A3LleFcWFwVBHBt+cot4Oc=
|
gopkg.in/Shopify/sarama.v1 v1.11.0/go.mod h1:AxnvoaevB2nBjNK17cG61A3LleFcWFwVBHBt+cot4Oc=
|
||||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20170727041045-23bcc3c4eae3/go.mod h1:3HH7i1SgMqlzxCcBmUHW657sD4Kvv9sC3HpL3YukzwA=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/h2non/bimg.v1 v1.0.18 h1:qn6/RpBHt+7WQqoBcK+aF2puc6nC78eZj5LexxoalT4=
|
||||||
gopkg.in/h2non/bimg.v1 v1.0.18/go.mod h1:PgsZL7dLwUbsGm1NYps320GxGgvQNTnecMCZqxV11So=
|
gopkg.in/h2non/bimg.v1 v1.0.18/go.mod h1:PgsZL7dLwUbsGm1NYps320GxGgvQNTnecMCZqxV11So=
|
||||||
|
gopkg.in/h2non/gock.v1 v1.0.14 h1:fTeu9fcUvSnLNacYvYI54h+1/XEteDyHvrVCZEEEYNM=
|
||||||
gopkg.in/h2non/gock.v1 v1.0.14/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
|
gopkg.in/h2non/gock.v1 v1.0.14/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
|
||||||
gopkg.in/macaroon.v2 v2.0.0/go.mod h1:+I6LnTMkm/uV5ew/0nsulNjL16SK4+C8yDmRUzHR17I=
|
|
||||||
gopkg.in/macaroon.v2 v2.1.0/go.mod h1:OUb+TQP/OP0WOerC2Jp/3CwhIKyIa9kQjuc7H24e6/o=
|
gopkg.in/macaroon.v2 v2.1.0/go.mod h1:OUb+TQP/OP0WOerC2Jp/3CwhIKyIa9kQjuc7H24e6/o=
|
||||||
gopkg.in/yaml.v2 v2.0.0-20171116090243-287cf08546ab h1:yZ6iByf7GKeJ3gsd1Dr/xaj1DyJ//wxKX1Cdh8LhoAw=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.0.0-20171116090243-287cf08546ab/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ func Download(
|
||||||
origin gomatrixserverlib.ServerName,
|
origin gomatrixserverlib.ServerName,
|
||||||
mediaID types.MediaID,
|
mediaID types.MediaID,
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
db *storage.Database,
|
db storage.Database,
|
||||||
client *gomatrixserverlib.Client,
|
client *gomatrixserverlib.Client,
|
||||||
activeRemoteRequests *types.ActiveRemoteRequests,
|
activeRemoteRequests *types.ActiveRemoteRequests,
|
||||||
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
||||||
|
|
@ -192,7 +192,7 @@ func (r *downloadRequest) doDownload(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
w http.ResponseWriter,
|
w http.ResponseWriter,
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
db *storage.Database,
|
db storage.Database,
|
||||||
client *gomatrixserverlib.Client,
|
client *gomatrixserverlib.Client,
|
||||||
activeRemoteRequests *types.ActiveRemoteRequests,
|
activeRemoteRequests *types.ActiveRemoteRequests,
|
||||||
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
||||||
|
|
@ -235,7 +235,7 @@ func (r *downloadRequest) respondFromLocalFile(
|
||||||
absBasePath config.Path,
|
absBasePath config.Path,
|
||||||
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
||||||
maxThumbnailGenerators int,
|
maxThumbnailGenerators int,
|
||||||
db *storage.Database,
|
db storage.Database,
|
||||||
dynamicThumbnails bool,
|
dynamicThumbnails bool,
|
||||||
thumbnailSizes []config.ThumbnailSize,
|
thumbnailSizes []config.ThumbnailSize,
|
||||||
) (*types.MediaMetadata, error) {
|
) (*types.MediaMetadata, error) {
|
||||||
|
|
@ -325,7 +325,7 @@ func (r *downloadRequest) getThumbnailFile(
|
||||||
filePath types.Path,
|
filePath types.Path,
|
||||||
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
||||||
maxThumbnailGenerators int,
|
maxThumbnailGenerators int,
|
||||||
db *storage.Database,
|
db storage.Database,
|
||||||
dynamicThumbnails bool,
|
dynamicThumbnails bool,
|
||||||
thumbnailSizes []config.ThumbnailSize,
|
thumbnailSizes []config.ThumbnailSize,
|
||||||
) (*os.File, *types.ThumbnailMetadata, error) {
|
) (*os.File, *types.ThumbnailMetadata, error) {
|
||||||
|
|
@ -407,7 +407,7 @@ func (r *downloadRequest) generateThumbnail(
|
||||||
thumbnailSize types.ThumbnailSize,
|
thumbnailSize types.ThumbnailSize,
|
||||||
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
||||||
maxThumbnailGenerators int,
|
maxThumbnailGenerators int,
|
||||||
db *storage.Database,
|
db storage.Database,
|
||||||
) (*types.ThumbnailMetadata, error) {
|
) (*types.ThumbnailMetadata, error) {
|
||||||
r.Logger.WithFields(log.Fields{
|
r.Logger.WithFields(log.Fields{
|
||||||
"Width": thumbnailSize.Width,
|
"Width": thumbnailSize.Width,
|
||||||
|
|
@ -443,7 +443,7 @@ func (r *downloadRequest) getRemoteFile(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
client *gomatrixserverlib.Client,
|
client *gomatrixserverlib.Client,
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
db *storage.Database,
|
db storage.Database,
|
||||||
activeRemoteRequests *types.ActiveRemoteRequests,
|
activeRemoteRequests *types.ActiveRemoteRequests,
|
||||||
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
||||||
) (errorResponse error) {
|
) (errorResponse error) {
|
||||||
|
|
@ -545,7 +545,7 @@ func (r *downloadRequest) fetchRemoteFileAndStoreMetadata(
|
||||||
client *gomatrixserverlib.Client,
|
client *gomatrixserverlib.Client,
|
||||||
absBasePath config.Path,
|
absBasePath config.Path,
|
||||||
maxFileSizeBytes config.FileSizeBytes,
|
maxFileSizeBytes config.FileSizeBytes,
|
||||||
db *storage.Database,
|
db storage.Database,
|
||||||
thumbnailSizes []config.ThumbnailSize,
|
thumbnailSizes []config.ThumbnailSize,
|
||||||
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
||||||
maxThumbnailGenerators int,
|
maxThumbnailGenerators int,
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ import (
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const pathPrefixR0 = "/_matrix/media/r0"
|
const pathPrefixR0 = "/_matrix/media/r0"
|
||||||
|
|
@ -41,7 +43,7 @@ const pathPrefixR0 = "/_matrix/media/r0"
|
||||||
func Setup(
|
func Setup(
|
||||||
apiMux *mux.Router,
|
apiMux *mux.Router,
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
db *storage.Database,
|
db storage.Database,
|
||||||
deviceDB *devices.Database,
|
deviceDB *devices.Database,
|
||||||
client *gomatrixserverlib.Client,
|
client *gomatrixserverlib.Client,
|
||||||
) {
|
) {
|
||||||
|
|
@ -78,19 +80,25 @@ func Setup(
|
||||||
func makeDownloadAPI(
|
func makeDownloadAPI(
|
||||||
name string,
|
name string,
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
db *storage.Database,
|
db storage.Database,
|
||||||
client *gomatrixserverlib.Client,
|
client *gomatrixserverlib.Client,
|
||||||
activeRemoteRequests *types.ActiveRemoteRequests,
|
activeRemoteRequests *types.ActiveRemoteRequests,
|
||||||
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
||||||
) http.HandlerFunc {
|
) http.HandlerFunc {
|
||||||
return prometheus.InstrumentHandler(name, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
counterVec := promauto.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: name,
|
||||||
|
Help: "Total number of media_api requests for either thumbnails or full downloads",
|
||||||
|
},
|
||||||
|
[]string{"code"},
|
||||||
|
)
|
||||||
|
httpHandler := func(w http.ResponseWriter, req *http.Request) {
|
||||||
req = util.RequestWithLogging(req)
|
req = util.RequestWithLogging(req)
|
||||||
|
|
||||||
// Set common headers returned regardless of the outcome of the request
|
// Set common headers returned regardless of the outcome of the request
|
||||||
util.SetCORSHeaders(w)
|
util.SetCORSHeaders(w)
|
||||||
// Content-Type will be overridden in case of returning file data, else we respond with JSON-formatted errors
|
// Content-Type will be overridden in case of returning file data, else we respond with JSON-formatted errors
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
vars, _ := common.URLDecodeMapValues(mux.Vars(req))
|
vars, _ := common.URLDecodeMapValues(mux.Vars(req))
|
||||||
Download(
|
Download(
|
||||||
w,
|
w,
|
||||||
|
|
@ -104,5 +112,6 @@ func makeDownloadAPI(
|
||||||
activeThumbnailGeneration,
|
activeThumbnailGeneration,
|
||||||
name == "thumbnail",
|
name == "thumbnail",
|
||||||
)
|
)
|
||||||
}))
|
}
|
||||||
|
return promhttp.InstrumentHandlerCounter(counterVec, http.HandlerFunc(httpHandler))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ type uploadResponse struct {
|
||||||
// This implementation supports a configurable maximum file size limit in bytes. If a user tries to upload more than this, they will receive an error that their upload is too large.
|
// This implementation supports a configurable maximum file size limit in bytes. If a user tries to upload more than this, they will receive an error that their upload is too large.
|
||||||
// Uploaded files are processed piece-wise to avoid DoS attacks which would starve the server of memory.
|
// Uploaded files are processed piece-wise to avoid DoS attacks which would starve the server of memory.
|
||||||
// TODO: We should time out requests if they have not received any data within a configured timeout period.
|
// TODO: We should time out requests if they have not received any data within a configured timeout period.
|
||||||
func Upload(req *http.Request, cfg *config.Dendrite, db *storage.Database, activeThumbnailGeneration *types.ActiveThumbnailGeneration) util.JSONResponse {
|
func Upload(req *http.Request, cfg *config.Dendrite, db storage.Database, activeThumbnailGeneration *types.ActiveThumbnailGeneration) util.JSONResponse {
|
||||||
r, resErr := parseAndValidateRequest(req, cfg)
|
r, resErr := parseAndValidateRequest(req, cfg)
|
||||||
if resErr != nil {
|
if resErr != nil {
|
||||||
return *resErr
|
return *resErr
|
||||||
|
|
@ -96,7 +96,7 @@ func (r *uploadRequest) doUpload(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
reqReader io.Reader,
|
reqReader io.Reader,
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
db *storage.Database,
|
db storage.Database,
|
||||||
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
||||||
) *util.JSONResponse {
|
) *util.JSONResponse {
|
||||||
r.Logger.WithFields(log.Fields{
|
r.Logger.WithFields(log.Fields{
|
||||||
|
|
@ -214,7 +214,7 @@ func (r *uploadRequest) storeFileAndMetadata(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
tmpDir types.Path,
|
tmpDir types.Path,
|
||||||
absBasePath config.Path,
|
absBasePath config.Path,
|
||||||
db *storage.Database,
|
db storage.Database,
|
||||||
thumbnailSizes []config.ThumbnailSize,
|
thumbnailSizes []config.ThumbnailSize,
|
||||||
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
||||||
maxThumbnailGenerators int,
|
maxThumbnailGenerators int,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -14,7 +15,7 @@
|
||||||
|
|
||||||
// FIXME: This should be made common!
|
// FIXME: This should be made common!
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
106
mediaapi/storage/postgres/storage.go
Normal file
106
mediaapi/storage/postgres/storage.go
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
// Import the postgres database driver.
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
"github.com/matrix-org/dendrite/mediaapi/types"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database is used to store metadata about a repository of media files.
|
||||||
|
type Database struct {
|
||||||
|
statements statements
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open opens a postgres database.
|
||||||
|
func Open(dataSourceName string) (*Database, error) {
|
||||||
|
var d Database
|
||||||
|
var err error
|
||||||
|
if d.db, err = sql.Open("postgres", dataSourceName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = d.statements.prepare(d.db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreMediaMetadata inserts the metadata about the uploaded media into the database.
|
||||||
|
// Returns an error if the combination of MediaID and Origin are not unique in the table.
|
||||||
|
func (d *Database) StoreMediaMetadata(
|
||||||
|
ctx context.Context, mediaMetadata *types.MediaMetadata,
|
||||||
|
) error {
|
||||||
|
return d.statements.media.insertMedia(ctx, mediaMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMediaMetadata returns metadata about media stored on this server.
|
||||||
|
// The media could have been uploaded to this server or fetched from another server and cached here.
|
||||||
|
// Returns nil metadata if there is no metadata associated with this media.
|
||||||
|
func (d *Database) GetMediaMetadata(
|
||||||
|
ctx context.Context, mediaID types.MediaID, mediaOrigin gomatrixserverlib.ServerName,
|
||||||
|
) (*types.MediaMetadata, error) {
|
||||||
|
mediaMetadata, err := d.statements.media.selectMedia(ctx, mediaID, mediaOrigin)
|
||||||
|
if err != nil && err == sql.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return mediaMetadata, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreThumbnail inserts the metadata about the thumbnail into the database.
|
||||||
|
// Returns an error if the combination of MediaID and Origin are not unique in the table.
|
||||||
|
func (d *Database) StoreThumbnail(
|
||||||
|
ctx context.Context, thumbnailMetadata *types.ThumbnailMetadata,
|
||||||
|
) error {
|
||||||
|
return d.statements.thumbnail.insertThumbnail(ctx, thumbnailMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetThumbnail returns metadata about a specific thumbnail.
|
||||||
|
// The media could have been uploaded to this server or fetched from another server and cached here.
|
||||||
|
// Returns nil metadata if there is no metadata associated with this thumbnail.
|
||||||
|
func (d *Database) GetThumbnail(
|
||||||
|
ctx context.Context,
|
||||||
|
mediaID types.MediaID,
|
||||||
|
mediaOrigin gomatrixserverlib.ServerName,
|
||||||
|
width, height int,
|
||||||
|
resizeMethod string,
|
||||||
|
) (*types.ThumbnailMetadata, error) {
|
||||||
|
thumbnailMetadata, err := d.statements.thumbnail.selectThumbnail(
|
||||||
|
ctx, mediaID, mediaOrigin, width, height, resizeMethod,
|
||||||
|
)
|
||||||
|
if err != nil && err == sql.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return thumbnailMetadata, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetThumbnails returns metadata about all thumbnails for a specific media stored on this server.
|
||||||
|
// The media could have been uploaded to this server or fetched from another server and cached here.
|
||||||
|
// Returns nil metadata if there are no thumbnails associated with this media.
|
||||||
|
func (d *Database) GetThumbnails(
|
||||||
|
ctx context.Context, mediaID types.MediaID, mediaOrigin gomatrixserverlib.ServerName,
|
||||||
|
) ([]*types.ThumbnailMetadata, error) {
|
||||||
|
thumbnails, err := d.statements.thumbnail.selectThumbnails(ctx, mediaID, mediaOrigin)
|
||||||
|
if err != nil && err == sql.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return thumbnails, err
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -16,90 +16,31 @@ package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"net/url"
|
||||||
|
|
||||||
// Import the postgres database driver.
|
"github.com/matrix-org/dendrite/mediaapi/storage/postgres"
|
||||||
_ "github.com/lib/pq"
|
|
||||||
"github.com/matrix-org/dendrite/mediaapi/types"
|
"github.com/matrix-org/dendrite/mediaapi/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Database is used to store metadata about a repository of media files.
|
type Database interface {
|
||||||
type Database struct {
|
StoreMediaMetadata(ctx context.Context, mediaMetadata *types.MediaMetadata) error
|
||||||
statements statements
|
GetMediaMetadata(ctx context.Context, mediaID types.MediaID, mediaOrigin gomatrixserverlib.ServerName) (*types.MediaMetadata, error)
|
||||||
db *sql.DB
|
StoreThumbnail(ctx context.Context, thumbnailMetadata *types.ThumbnailMetadata) error
|
||||||
|
GetThumbnail(ctx context.Context, mediaID types.MediaID, mediaOrigin gomatrixserverlib.ServerName, width, height int, resizeMethod string) (*types.ThumbnailMetadata, error)
|
||||||
|
GetThumbnails(ctx context.Context, mediaID types.MediaID, mediaOrigin gomatrixserverlib.ServerName) ([]*types.ThumbnailMetadata, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open opens a postgres database.
|
// Open opens a postgres database.
|
||||||
func Open(dataSourceName string) (*Database, error) {
|
func Open(dataSourceName string) (Database, error) {
|
||||||
var d Database
|
uri, err := url.Parse(dataSourceName)
|
||||||
var err error
|
if err != nil {
|
||||||
if d.db, err = sql.Open("postgres", dataSourceName); err != nil {
|
return postgres.Open(dataSourceName)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
if err = d.statements.prepare(d.db); err != nil {
|
switch uri.Scheme {
|
||||||
return nil, err
|
case "postgres":
|
||||||
|
return postgres.Open(dataSourceName)
|
||||||
|
default:
|
||||||
|
return postgres.Open(dataSourceName)
|
||||||
}
|
}
|
||||||
return &d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreMediaMetadata inserts the metadata about the uploaded media into the database.
|
|
||||||
// Returns an error if the combination of MediaID and Origin are not unique in the table.
|
|
||||||
func (d *Database) StoreMediaMetadata(
|
|
||||||
ctx context.Context, mediaMetadata *types.MediaMetadata,
|
|
||||||
) error {
|
|
||||||
return d.statements.media.insertMedia(ctx, mediaMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMediaMetadata returns metadata about media stored on this server.
|
|
||||||
// The media could have been uploaded to this server or fetched from another server and cached here.
|
|
||||||
// Returns nil metadata if there is no metadata associated with this media.
|
|
||||||
func (d *Database) GetMediaMetadata(
|
|
||||||
ctx context.Context, mediaID types.MediaID, mediaOrigin gomatrixserverlib.ServerName,
|
|
||||||
) (*types.MediaMetadata, error) {
|
|
||||||
mediaMetadata, err := d.statements.media.selectMedia(ctx, mediaID, mediaOrigin)
|
|
||||||
if err != nil && err == sql.ErrNoRows {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return mediaMetadata, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreThumbnail inserts the metadata about the thumbnail into the database.
|
|
||||||
// Returns an error if the combination of MediaID and Origin are not unique in the table.
|
|
||||||
func (d *Database) StoreThumbnail(
|
|
||||||
ctx context.Context, thumbnailMetadata *types.ThumbnailMetadata,
|
|
||||||
) error {
|
|
||||||
return d.statements.thumbnail.insertThumbnail(ctx, thumbnailMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetThumbnail returns metadata about a specific thumbnail.
|
|
||||||
// The media could have been uploaded to this server or fetched from another server and cached here.
|
|
||||||
// Returns nil metadata if there is no metadata associated with this thumbnail.
|
|
||||||
func (d *Database) GetThumbnail(
|
|
||||||
ctx context.Context,
|
|
||||||
mediaID types.MediaID,
|
|
||||||
mediaOrigin gomatrixserverlib.ServerName,
|
|
||||||
width, height int,
|
|
||||||
resizeMethod string,
|
|
||||||
) (*types.ThumbnailMetadata, error) {
|
|
||||||
thumbnailMetadata, err := d.statements.thumbnail.selectThumbnail(
|
|
||||||
ctx, mediaID, mediaOrigin, width, height, resizeMethod,
|
|
||||||
)
|
|
||||||
if err != nil && err == sql.ErrNoRows {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return thumbnailMetadata, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetThumbnails returns metadata about all thumbnails for a specific media stored on this server.
|
|
||||||
// The media could have been uploaded to this server or fetched from another server and cached here.
|
|
||||||
// Returns nil metadata if there are no thumbnails associated with this media.
|
|
||||||
func (d *Database) GetThumbnails(
|
|
||||||
ctx context.Context, mediaID types.MediaID, mediaOrigin gomatrixserverlib.ServerName,
|
|
||||||
) ([]*types.ThumbnailMetadata, error) {
|
|
||||||
thumbnails, err := d.statements.thumbnail.selectThumbnails(ctx, mediaID, mediaOrigin)
|
|
||||||
if err != nil && err == sql.ErrNoRows {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return thumbnails, err
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ func isThumbnailExists(
|
||||||
dst types.Path,
|
dst types.Path,
|
||||||
config types.ThumbnailSize,
|
config types.ThumbnailSize,
|
||||||
mediaMetadata *types.MediaMetadata,
|
mediaMetadata *types.MediaMetadata,
|
||||||
db *storage.Database,
|
db storage.Database,
|
||||||
logger *log.Entry,
|
logger *log.Entry,
|
||||||
) (bool, error) {
|
) (bool, error) {
|
||||||
thumbnailMetadata, err := db.GetThumbnail(
|
thumbnailMetadata, err := db.GetThumbnail(
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ func GenerateThumbnails(
|
||||||
mediaMetadata *types.MediaMetadata,
|
mediaMetadata *types.MediaMetadata,
|
||||||
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
||||||
maxThumbnailGenerators int,
|
maxThumbnailGenerators int,
|
||||||
db *storage.Database,
|
db storage.Database,
|
||||||
logger *log.Entry,
|
logger *log.Entry,
|
||||||
) (busy bool, errorReturn error) {
|
) (busy bool, errorReturn error) {
|
||||||
img, err := readFile(string(src))
|
img, err := readFile(string(src))
|
||||||
|
|
@ -78,7 +78,7 @@ func GenerateThumbnail(
|
||||||
mediaMetadata *types.MediaMetadata,
|
mediaMetadata *types.MediaMetadata,
|
||||||
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
||||||
maxThumbnailGenerators int,
|
maxThumbnailGenerators int,
|
||||||
db *storage.Database,
|
db storage.Database,
|
||||||
logger *log.Entry,
|
logger *log.Entry,
|
||||||
) (busy bool, errorReturn error) {
|
) (busy bool, errorReturn error) {
|
||||||
img, err := readFile(string(src))
|
img, err := readFile(string(src))
|
||||||
|
|
@ -142,7 +142,7 @@ func createThumbnail(
|
||||||
mediaMetadata *types.MediaMetadata,
|
mediaMetadata *types.MediaMetadata,
|
||||||
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
activeThumbnailGeneration *types.ActiveThumbnailGeneration,
|
||||||
maxThumbnailGenerators int,
|
maxThumbnailGenerators int,
|
||||||
db *storage.Database,
|
db storage.Database,
|
||||||
logger *log.Entry,
|
logger *log.Entry,
|
||||||
) (busy bool, errorReturn error) {
|
) (busy bool, errorReturn error) {
|
||||||
logger = logger.WithFields(log.Fields{
|
logger = logger.WithFields(log.Fields{
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import (
|
||||||
// OutputRoomEventConsumer consumes events that originated in the room server.
|
// OutputRoomEventConsumer consumes events that originated in the room server.
|
||||||
type OutputRoomEventConsumer struct {
|
type OutputRoomEventConsumer struct {
|
||||||
roomServerConsumer *common.ContinualConsumer
|
roomServerConsumer *common.ContinualConsumer
|
||||||
db *storage.PublicRoomsServerDatabase
|
db storage.Database
|
||||||
query api.RoomserverQueryAPI
|
query api.RoomserverQueryAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@ type OutputRoomEventConsumer struct {
|
||||||
func NewOutputRoomEventConsumer(
|
func NewOutputRoomEventConsumer(
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
kafkaConsumer sarama.Consumer,
|
kafkaConsumer sarama.Consumer,
|
||||||
store *storage.PublicRoomsServerDatabase,
|
store storage.Database,
|
||||||
queryAPI api.RoomserverQueryAPI,
|
queryAPI api.RoomserverQueryAPI,
|
||||||
) *OutputRoomEventConsumer {
|
) *OutputRoomEventConsumer {
|
||||||
consumer := common.ContinualConsumer{
|
consumer := common.ContinualConsumer{
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ type roomVisibility struct {
|
||||||
|
|
||||||
// GetVisibility implements GET /directory/list/room/{roomID}
|
// GetVisibility implements GET /directory/list/room/{roomID}
|
||||||
func GetVisibility(
|
func GetVisibility(
|
||||||
req *http.Request, publicRoomsDatabase *storage.PublicRoomsServerDatabase,
|
req *http.Request, publicRoomsDatabase storage.Database,
|
||||||
roomID string,
|
roomID string,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
isPublic, err := publicRoomsDatabase.GetRoomVisibility(req.Context(), roomID)
|
isPublic, err := publicRoomsDatabase.GetRoomVisibility(req.Context(), roomID)
|
||||||
|
|
@ -54,7 +54,7 @@ func GetVisibility(
|
||||||
// SetVisibility implements PUT /directory/list/room/{roomID}
|
// SetVisibility implements PUT /directory/list/room/{roomID}
|
||||||
// TODO: Check if user has the power level to edit the room visibility
|
// TODO: Check if user has the power level to edit the room visibility
|
||||||
func SetVisibility(
|
func SetVisibility(
|
||||||
req *http.Request, publicRoomsDatabase *storage.PublicRoomsServerDatabase,
|
req *http.Request, publicRoomsDatabase storage.Database,
|
||||||
roomID string,
|
roomID string,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
var v roomVisibility
|
var v roomVisibility
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ type publicRoomRes struct {
|
||||||
|
|
||||||
// GetPostPublicRooms implements GET and POST /publicRooms
|
// GetPostPublicRooms implements GET and POST /publicRooms
|
||||||
func GetPostPublicRooms(
|
func GetPostPublicRooms(
|
||||||
req *http.Request, publicRoomDatabase *storage.PublicRoomsServerDatabase,
|
req *http.Request, publicRoomDatabase storage.Database,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
var limit int16
|
var limit int16
|
||||||
var offset int64
|
var offset int64
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,10 @@ package publicroomsapi
|
||||||
import (
|
import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
||||||
"github.com/matrix-org/dendrite/common/basecomponent"
|
"github.com/matrix-org/dendrite/common/basecomponent"
|
||||||
|
"github.com/matrix-org/dendrite/publicroomsapi/consumers"
|
||||||
"github.com/matrix-org/dendrite/publicroomsapi/routing"
|
"github.com/matrix-org/dendrite/publicroomsapi/routing"
|
||||||
"github.com/matrix-org/dendrite/publicroomsapi/storage"
|
"github.com/matrix-org/dendrite/publicroomsapi/storage"
|
||||||
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -27,11 +29,19 @@ import (
|
||||||
func SetupPublicRoomsAPIComponent(
|
func SetupPublicRoomsAPIComponent(
|
||||||
base *basecomponent.BaseDendrite,
|
base *basecomponent.BaseDendrite,
|
||||||
deviceDB *devices.Database,
|
deviceDB *devices.Database,
|
||||||
|
rsQueryAPI roomserverAPI.RoomserverQueryAPI,
|
||||||
) {
|
) {
|
||||||
publicRoomsDB, err := storage.NewPublicRoomsServerDatabase(string(base.Cfg.Database.PublicRoomsAPI))
|
publicRoomsDB, err := storage.NewPublicRoomsServerDatabase(string(base.Cfg.Database.PublicRoomsAPI))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Panicf("failed to connect to public rooms db")
|
logrus.WithError(err).Panicf("failed to connect to public rooms db")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rsConsumer := consumers.NewOutputRoomEventConsumer(
|
||||||
|
base.Cfg, base.KafkaConsumer, publicRoomsDB, rsQueryAPI,
|
||||||
|
)
|
||||||
|
if err = rsConsumer.Start(); err != nil {
|
||||||
|
logrus.WithError(err).Panic("failed to start public rooms server consumer")
|
||||||
|
}
|
||||||
|
|
||||||
routing.Setup(base.APIMux, deviceDB, publicRoomsDB)
|
routing.Setup(base.APIMux, deviceDB, publicRoomsDB)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ const pathPrefixR0 = "/_matrix/client/r0"
|
||||||
// Due to Setup being used to call many other functions, a gocyclo nolint is
|
// Due to Setup being used to call many other functions, a gocyclo nolint is
|
||||||
// applied:
|
// applied:
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
func Setup(apiMux *mux.Router, deviceDB *devices.Database, publicRoomsDB *storage.PublicRoomsServerDatabase) {
|
func Setup(apiMux *mux.Router, deviceDB *devices.Database, publicRoomsDB storage.Database) {
|
||||||
r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter()
|
r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter()
|
||||||
|
|
||||||
authData := auth.Data{
|
authData := auth.Data{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
254
publicroomsapi/storage/postgres/storage.go
Normal file
254
publicroomsapi/storage/postgres/storage.go
Normal file
|
|
@ -0,0 +1,254 @@
|
||||||
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/dendrite/publicroomsapi/types"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublicRoomsServerDatabase represents a public rooms server database.
|
||||||
|
type PublicRoomsServerDatabase struct {
|
||||||
|
db *sql.DB
|
||||||
|
common.PartitionOffsetStatements
|
||||||
|
statements publicRoomsStatements
|
||||||
|
}
|
||||||
|
|
||||||
|
type attributeValue interface{}
|
||||||
|
|
||||||
|
// NewPublicRoomsServerDatabase creates a new public rooms server database.
|
||||||
|
func NewPublicRoomsServerDatabase(dataSourceName string) (*PublicRoomsServerDatabase, error) {
|
||||||
|
var db *sql.DB
|
||||||
|
var err error
|
||||||
|
if db, err = sql.Open("postgres", dataSourceName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
storage := PublicRoomsServerDatabase{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
if err = storage.PartitionOffsetStatements.Prepare(db, "publicroomsapi"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = storage.statements.prepare(db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &storage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoomVisibility returns the room visibility as a boolean: true if the room
|
||||||
|
// is publicly visible, false if not.
|
||||||
|
// Returns an error if the retrieval failed.
|
||||||
|
func (d *PublicRoomsServerDatabase) GetRoomVisibility(
|
||||||
|
ctx context.Context, roomID string,
|
||||||
|
) (bool, error) {
|
||||||
|
return d.statements.selectRoomVisibility(ctx, roomID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRoomVisibility updates the visibility attribute of a room. This attribute
|
||||||
|
// must be set to true if the room is publicly visible, false if not.
|
||||||
|
// Returns an error if the update failed.
|
||||||
|
func (d *PublicRoomsServerDatabase) SetRoomVisibility(
|
||||||
|
ctx context.Context, visible bool, roomID string,
|
||||||
|
) error {
|
||||||
|
return d.statements.updateRoomAttribute(ctx, "visibility", visible, roomID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountPublicRooms returns the number of room set as publicly visible on the server.
|
||||||
|
// Returns an error if the retrieval failed.
|
||||||
|
func (d *PublicRoomsServerDatabase) CountPublicRooms(ctx context.Context) (int64, error) {
|
||||||
|
return d.statements.countPublicRooms(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPublicRooms returns an array containing the local rooms set as publicly visible, ordered by their number
|
||||||
|
// of joined members. This array can be limited by a given number of elements, and offset by a given value.
|
||||||
|
// If the limit is 0, doesn't limit the number of results. If the offset is 0 too, the array contains all
|
||||||
|
// the rooms set as publicly visible on the server.
|
||||||
|
// Returns an error if the retrieval failed.
|
||||||
|
func (d *PublicRoomsServerDatabase) GetPublicRooms(
|
||||||
|
ctx context.Context, offset int64, limit int16, filter string,
|
||||||
|
) ([]types.PublicRoom, error) {
|
||||||
|
return d.statements.selectPublicRooms(ctx, offset, limit, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRoomFromEvents iterate over a slice of state events and call
|
||||||
|
// UpdateRoomFromEvent on each of them to update the database representation of
|
||||||
|
// the rooms updated by each event.
|
||||||
|
// The slice of events to remove is used to update the number of joined members
|
||||||
|
// for the room in the database.
|
||||||
|
// If the update triggered by one of the events failed, aborts the process and
|
||||||
|
// returns an error.
|
||||||
|
func (d *PublicRoomsServerDatabase) UpdateRoomFromEvents(
|
||||||
|
ctx context.Context,
|
||||||
|
eventsToAdd []gomatrixserverlib.Event,
|
||||||
|
eventsToRemove []gomatrixserverlib.Event,
|
||||||
|
) error {
|
||||||
|
for _, event := range eventsToAdd {
|
||||||
|
if err := d.UpdateRoomFromEvent(ctx, event); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, event := range eventsToRemove {
|
||||||
|
if event.Type() == "m.room.member" {
|
||||||
|
if err := d.updateNumJoinedUsers(ctx, event, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRoomFromEvent updates the database representation of a room from a Matrix event, by
|
||||||
|
// checking the event's type to know which attribute to change and using the event's content
|
||||||
|
// to define the new value of the attribute.
|
||||||
|
// If the event doesn't match with any property used to compute the public room directory,
|
||||||
|
// does nothing.
|
||||||
|
// If something went wrong during the process, returns an error.
|
||||||
|
func (d *PublicRoomsServerDatabase) UpdateRoomFromEvent(
|
||||||
|
ctx context.Context, event gomatrixserverlib.Event,
|
||||||
|
) error {
|
||||||
|
// Process the event according to its type
|
||||||
|
switch event.Type() {
|
||||||
|
case "m.room.create":
|
||||||
|
return d.statements.insertNewRoom(ctx, event.RoomID())
|
||||||
|
case "m.room.member":
|
||||||
|
return d.updateNumJoinedUsers(ctx, event, false)
|
||||||
|
case "m.room.aliases":
|
||||||
|
return d.updateRoomAliases(ctx, event)
|
||||||
|
case "m.room.canonical_alias":
|
||||||
|
var content common.CanonicalAliasContent
|
||||||
|
field := &(content.Alias)
|
||||||
|
attrName := "canonical_alias"
|
||||||
|
return d.updateStringAttribute(ctx, attrName, event, &content, field)
|
||||||
|
case "m.room.name":
|
||||||
|
var content common.NameContent
|
||||||
|
field := &(content.Name)
|
||||||
|
attrName := "name"
|
||||||
|
return d.updateStringAttribute(ctx, attrName, event, &content, field)
|
||||||
|
case "m.room.topic":
|
||||||
|
var content common.TopicContent
|
||||||
|
field := &(content.Topic)
|
||||||
|
attrName := "topic"
|
||||||
|
return d.updateStringAttribute(ctx, attrName, event, &content, field)
|
||||||
|
case "m.room.avatar":
|
||||||
|
var content common.AvatarContent
|
||||||
|
field := &(content.URL)
|
||||||
|
attrName := "avatar_url"
|
||||||
|
return d.updateStringAttribute(ctx, attrName, event, &content, field)
|
||||||
|
case "m.room.history_visibility":
|
||||||
|
var content common.HistoryVisibilityContent
|
||||||
|
field := &(content.HistoryVisibility)
|
||||||
|
attrName := "world_readable"
|
||||||
|
strForTrue := "world_readable"
|
||||||
|
return d.updateBooleanAttribute(ctx, attrName, event, &content, field, strForTrue)
|
||||||
|
case "m.room.guest_access":
|
||||||
|
var content common.GuestAccessContent
|
||||||
|
field := &(content.GuestAccess)
|
||||||
|
attrName := "guest_can_join"
|
||||||
|
strForTrue := "can_join"
|
||||||
|
return d.updateBooleanAttribute(ctx, attrName, event, &content, field, strForTrue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the event type didn't match, return with no error
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateNumJoinedUsers updates the number of joined user in the database representation
|
||||||
|
// of a room using a given "m.room.member" Matrix event.
|
||||||
|
// If the membership property of the event isn't "join", ignores it and returs nil.
|
||||||
|
// If the remove parameter is set to false, increments the joined members counter in the
|
||||||
|
// database, if set to truem decrements it.
|
||||||
|
// Returns an error if the update failed.
|
||||||
|
func (d *PublicRoomsServerDatabase) updateNumJoinedUsers(
|
||||||
|
ctx context.Context, membershipEvent gomatrixserverlib.Event, remove bool,
|
||||||
|
) error {
|
||||||
|
membership, err := membershipEvent.Membership()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if membership != gomatrixserverlib.Join {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if remove {
|
||||||
|
return d.statements.decrementJoinedMembersInRoom(ctx, membershipEvent.RoomID())
|
||||||
|
}
|
||||||
|
return d.statements.incrementJoinedMembersInRoom(ctx, membershipEvent.RoomID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateStringAttribute updates a given string attribute in the database
|
||||||
|
// representation of a room using a given string data field from content of the
|
||||||
|
// Matrix event triggering the update.
|
||||||
|
// Returns an error if decoding the Matrix event's content or updating the attribute
|
||||||
|
// failed.
|
||||||
|
func (d *PublicRoomsServerDatabase) updateStringAttribute(
|
||||||
|
ctx context.Context, attrName string, event gomatrixserverlib.Event,
|
||||||
|
content interface{}, field *string,
|
||||||
|
) error {
|
||||||
|
if err := json.Unmarshal(event.Content(), content); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.statements.updateRoomAttribute(ctx, attrName, *field, event.RoomID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateBooleanAttribute updates a given boolean attribute in the database
|
||||||
|
// representation of a room using a given string data field from content of the
|
||||||
|
// Matrix event triggering the update.
|
||||||
|
// The attribute is set to true if the field matches a given string, false if not.
|
||||||
|
// Returns an error if decoding the Matrix event's content or updating the attribute
|
||||||
|
// failed.
|
||||||
|
func (d *PublicRoomsServerDatabase) updateBooleanAttribute(
|
||||||
|
ctx context.Context, attrName string, event gomatrixserverlib.Event,
|
||||||
|
content interface{}, field *string, strForTrue string,
|
||||||
|
) error {
|
||||||
|
if err := json.Unmarshal(event.Content(), content); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var attrValue bool
|
||||||
|
if *field == strForTrue {
|
||||||
|
attrValue = true
|
||||||
|
} else {
|
||||||
|
attrValue = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.statements.updateRoomAttribute(ctx, attrName, attrValue, event.RoomID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateRoomAliases decodes the content of a "m.room.aliases" Matrix event and update the list of aliases of
|
||||||
|
// a given room with it.
|
||||||
|
// Returns an error if decoding the Matrix event or updating the list failed.
|
||||||
|
func (d *PublicRoomsServerDatabase) updateRoomAliases(
|
||||||
|
ctx context.Context, aliasesEvent gomatrixserverlib.Event,
|
||||||
|
) error {
|
||||||
|
var content common.AliasesContent
|
||||||
|
if err := json.Unmarshal(aliasesEvent.Content(), &content); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.statements.updateRoomAttribute(
|
||||||
|
ctx, "aliases", content.Aliases, aliasesEvent.RoomID(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -16,237 +16,34 @@ package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"net/url"
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/dendrite/publicroomsapi/storage/postgres"
|
||||||
"github.com/matrix-org/dendrite/publicroomsapi/types"
|
"github.com/matrix-org/dendrite/publicroomsapi/types"
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PublicRoomsServerDatabase represents a public rooms server database.
|
type Database interface {
|
||||||
type PublicRoomsServerDatabase struct {
|
common.PartitionStorer
|
||||||
db *sql.DB
|
GetRoomVisibility(ctx context.Context, roomID string) (bool, error)
|
||||||
common.PartitionOffsetStatements
|
SetRoomVisibility(ctx context.Context, visible bool, roomID string) error
|
||||||
statements publicRoomsStatements
|
CountPublicRooms(ctx context.Context) (int64, error)
|
||||||
|
GetPublicRooms(ctx context.Context, offset int64, limit int16, filter string) ([]types.PublicRoom, error)
|
||||||
|
UpdateRoomFromEvents(ctx context.Context, eventsToAdd []gomatrixserverlib.Event, eventsToRemove []gomatrixserverlib.Event) error
|
||||||
|
UpdateRoomFromEvent(ctx context.Context, event gomatrixserverlib.Event) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type attributeValue interface{}
|
// NewPublicRoomsServerDatabase opens a database connection.
|
||||||
|
func NewPublicRoomsServerDatabase(dataSourceName string) (Database, error) {
|
||||||
// NewPublicRoomsServerDatabase creates a new public rooms server database.
|
uri, err := url.Parse(dataSourceName)
|
||||||
func NewPublicRoomsServerDatabase(dataSourceName string) (*PublicRoomsServerDatabase, error) {
|
|
||||||
var db *sql.DB
|
|
||||||
var err error
|
|
||||||
if db, err = sql.Open("postgres", dataSourceName); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
partitions := common.PartitionOffsetStatements{}
|
|
||||||
if err = partitions.Prepare(db, "publicroomsapi"); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
statements := publicRoomsStatements{}
|
|
||||||
if err = statements.prepare(db); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &PublicRoomsServerDatabase{db, partitions, statements}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRoomVisibility returns the room visibility as a boolean: true if the room
|
|
||||||
// is publicly visible, false if not.
|
|
||||||
// Returns an error if the retrieval failed.
|
|
||||||
func (d *PublicRoomsServerDatabase) GetRoomVisibility(
|
|
||||||
ctx context.Context, roomID string,
|
|
||||||
) (bool, error) {
|
|
||||||
return d.statements.selectRoomVisibility(ctx, roomID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRoomVisibility updates the visibility attribute of a room. This attribute
|
|
||||||
// must be set to true if the room is publicly visible, false if not.
|
|
||||||
// Returns an error if the update failed.
|
|
||||||
func (d *PublicRoomsServerDatabase) SetRoomVisibility(
|
|
||||||
ctx context.Context, visible bool, roomID string,
|
|
||||||
) error {
|
|
||||||
return d.statements.updateRoomAttribute(ctx, "visibility", visible, roomID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CountPublicRooms returns the number of room set as publicly visible on the server.
|
|
||||||
// Returns an error if the retrieval failed.
|
|
||||||
func (d *PublicRoomsServerDatabase) CountPublicRooms(ctx context.Context) (int64, error) {
|
|
||||||
return d.statements.countPublicRooms(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPublicRooms returns an array containing the local rooms set as publicly visible, ordered by their number
|
|
||||||
// of joined members. This array can be limited by a given number of elements, and offset by a given value.
|
|
||||||
// If the limit is 0, doesn't limit the number of results. If the offset is 0 too, the array contains all
|
|
||||||
// the rooms set as publicly visible on the server.
|
|
||||||
// Returns an error if the retrieval failed.
|
|
||||||
func (d *PublicRoomsServerDatabase) GetPublicRooms(
|
|
||||||
ctx context.Context, offset int64, limit int16, filter string,
|
|
||||||
) ([]types.PublicRoom, error) {
|
|
||||||
return d.statements.selectPublicRooms(ctx, offset, limit, filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateRoomFromEvents iterate over a slice of state events and call
|
|
||||||
// UpdateRoomFromEvent on each of them to update the database representation of
|
|
||||||
// the rooms updated by each event.
|
|
||||||
// The slice of events to remove is used to update the number of joined members
|
|
||||||
// for the room in the database.
|
|
||||||
// If the update triggered by one of the events failed, aborts the process and
|
|
||||||
// returns an error.
|
|
||||||
func (d *PublicRoomsServerDatabase) UpdateRoomFromEvents(
|
|
||||||
ctx context.Context,
|
|
||||||
eventsToAdd []gomatrixserverlib.Event,
|
|
||||||
eventsToRemove []gomatrixserverlib.Event,
|
|
||||||
) error {
|
|
||||||
for _, event := range eventsToAdd {
|
|
||||||
if err := d.UpdateRoomFromEvent(ctx, event); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, event := range eventsToRemove {
|
|
||||||
if event.Type() == "m.room.member" {
|
|
||||||
if err := d.updateNumJoinedUsers(ctx, event, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateRoomFromEvent updates the database representation of a room from a Matrix event, by
|
|
||||||
// checking the event's type to know which attribute to change and using the event's content
|
|
||||||
// to define the new value of the attribute.
|
|
||||||
// If the event doesn't match with any property used to compute the public room directory,
|
|
||||||
// does nothing.
|
|
||||||
// If something went wrong during the process, returns an error.
|
|
||||||
func (d *PublicRoomsServerDatabase) UpdateRoomFromEvent(
|
|
||||||
ctx context.Context, event gomatrixserverlib.Event,
|
|
||||||
) error {
|
|
||||||
// Process the event according to its type
|
|
||||||
switch event.Type() {
|
|
||||||
case "m.room.create":
|
|
||||||
return d.statements.insertNewRoom(ctx, event.RoomID())
|
|
||||||
case "m.room.member":
|
|
||||||
return d.updateNumJoinedUsers(ctx, event, false)
|
|
||||||
case "m.room.aliases":
|
|
||||||
return d.updateRoomAliases(ctx, event)
|
|
||||||
case "m.room.canonical_alias":
|
|
||||||
var content common.CanonicalAliasContent
|
|
||||||
field := &(content.Alias)
|
|
||||||
attrName := "canonical_alias"
|
|
||||||
return d.updateStringAttribute(ctx, attrName, event, &content, field)
|
|
||||||
case "m.room.name":
|
|
||||||
var content common.NameContent
|
|
||||||
field := &(content.Name)
|
|
||||||
attrName := "name"
|
|
||||||
return d.updateStringAttribute(ctx, attrName, event, &content, field)
|
|
||||||
case "m.room.topic":
|
|
||||||
var content common.TopicContent
|
|
||||||
field := &(content.Topic)
|
|
||||||
attrName := "topic"
|
|
||||||
return d.updateStringAttribute(ctx, attrName, event, &content, field)
|
|
||||||
case "m.room.avatar":
|
|
||||||
var content common.AvatarContent
|
|
||||||
field := &(content.URL)
|
|
||||||
attrName := "avatar_url"
|
|
||||||
return d.updateStringAttribute(ctx, attrName, event, &content, field)
|
|
||||||
case "m.room.history_visibility":
|
|
||||||
var content common.HistoryVisibilityContent
|
|
||||||
field := &(content.HistoryVisibility)
|
|
||||||
attrName := "world_readable"
|
|
||||||
strForTrue := "world_readable"
|
|
||||||
return d.updateBooleanAttribute(ctx, attrName, event, &content, field, strForTrue)
|
|
||||||
case "m.room.guest_access":
|
|
||||||
var content common.GuestAccessContent
|
|
||||||
field := &(content.GuestAccess)
|
|
||||||
attrName := "guest_can_join"
|
|
||||||
strForTrue := "can_join"
|
|
||||||
return d.updateBooleanAttribute(ctx, attrName, event, &content, field, strForTrue)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the event type didn't match, return with no error
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateNumJoinedUsers updates the number of joined user in the database representation
|
|
||||||
// of a room using a given "m.room.member" Matrix event.
|
|
||||||
// If the membership property of the event isn't "join", ignores it and returs nil.
|
|
||||||
// If the remove parameter is set to false, increments the joined members counter in the
|
|
||||||
// database, if set to truem decrements it.
|
|
||||||
// Returns an error if the update failed.
|
|
||||||
func (d *PublicRoomsServerDatabase) updateNumJoinedUsers(
|
|
||||||
ctx context.Context, membershipEvent gomatrixserverlib.Event, remove bool,
|
|
||||||
) error {
|
|
||||||
membership, err := membershipEvent.Membership()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return postgres.NewPublicRoomsServerDatabase(dataSourceName)
|
||||||
}
|
}
|
||||||
|
switch uri.Scheme {
|
||||||
if membership != gomatrixserverlib.Join {
|
case "postgres":
|
||||||
return nil
|
return postgres.NewPublicRoomsServerDatabase(dataSourceName)
|
||||||
|
default:
|
||||||
|
return postgres.NewPublicRoomsServerDatabase(dataSourceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if remove {
|
|
||||||
return d.statements.decrementJoinedMembersInRoom(ctx, membershipEvent.RoomID())
|
|
||||||
}
|
|
||||||
return d.statements.incrementJoinedMembersInRoom(ctx, membershipEvent.RoomID())
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateStringAttribute updates a given string attribute in the database
|
|
||||||
// representation of a room using a given string data field from content of the
|
|
||||||
// Matrix event triggering the update.
|
|
||||||
// Returns an error if decoding the Matrix event's content or updating the attribute
|
|
||||||
// failed.
|
|
||||||
func (d *PublicRoomsServerDatabase) updateStringAttribute(
|
|
||||||
ctx context.Context, attrName string, event gomatrixserverlib.Event,
|
|
||||||
content interface{}, field *string,
|
|
||||||
) error {
|
|
||||||
if err := json.Unmarshal(event.Content(), content); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.statements.updateRoomAttribute(ctx, attrName, *field, event.RoomID())
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateBooleanAttribute updates a given boolean attribute in the database
|
|
||||||
// representation of a room using a given string data field from content of the
|
|
||||||
// Matrix event triggering the update.
|
|
||||||
// The attribute is set to true if the field matches a given string, false if not.
|
|
||||||
// Returns an error if decoding the Matrix event's content or updating the attribute
|
|
||||||
// failed.
|
|
||||||
func (d *PublicRoomsServerDatabase) updateBooleanAttribute(
|
|
||||||
ctx context.Context, attrName string, event gomatrixserverlib.Event,
|
|
||||||
content interface{}, field *string, strForTrue string,
|
|
||||||
) error {
|
|
||||||
if err := json.Unmarshal(event.Content(), content); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var attrValue bool
|
|
||||||
if *field == strForTrue {
|
|
||||||
attrValue = true
|
|
||||||
} else {
|
|
||||||
attrValue = false
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.statements.updateRoomAttribute(ctx, attrName, attrValue, event.RoomID())
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateRoomAliases decodes the content of a "m.room.aliases" Matrix event and update the list of aliases of
|
|
||||||
// a given room with it.
|
|
||||||
// Returns an error if decoding the Matrix event or updating the list failed.
|
|
||||||
func (d *PublicRoomsServerDatabase) updateRoomAliases(
|
|
||||||
ctx context.Context, aliasesEvent gomatrixserverlib.Event,
|
|
||||||
) error {
|
|
||||||
var content common.AliasesContent
|
|
||||||
if err := json.Unmarshal(aliasesEvent.Content(), &content); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.statements.updateRoomAttribute(
|
|
||||||
ctx, "aliases", content.Aliases, aliasesEvent.RoomID(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
// Copyright 2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -230,6 +232,29 @@ type QueryBackfillResponse struct {
|
||||||
Events []gomatrixserverlib.Event `json:"events"`
|
Events []gomatrixserverlib.Event `json:"events"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryServersInRoomAtEventRequest is a request to QueryServersInRoomAtEvent
|
||||||
|
type QueryServersInRoomAtEventRequest struct {
|
||||||
|
// ID of the room to retrieve member servers for.
|
||||||
|
RoomID string `json:"room_id"`
|
||||||
|
// ID of the event for which to retrieve member servers.
|
||||||
|
EventID string `json:"event_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryServersInRoomAtEventResponse is a response to QueryServersInRoomAtEvent
|
||||||
|
type QueryServersInRoomAtEventResponse struct {
|
||||||
|
// Servers present in the room for these events.
|
||||||
|
Servers []gomatrixserverlib.ServerName `json:"servers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRoomVersionCapabilities asks for the default room version
|
||||||
|
type QueryRoomVersionCapabilitiesRequest struct{}
|
||||||
|
|
||||||
|
// QueryRoomVersionCapabilitiesResponse is a response to QueryServersInRoomAtEventResponse
|
||||||
|
type QueryRoomVersionCapabilitiesResponse struct {
|
||||||
|
DefaultRoomVersion string `json:"default"`
|
||||||
|
AvailableRoomVersions map[string]string `json:"available"`
|
||||||
|
}
|
||||||
|
|
||||||
// RoomserverQueryAPI is used to query information from the room server.
|
// RoomserverQueryAPI is used to query information from the room server.
|
||||||
type RoomserverQueryAPI interface {
|
type RoomserverQueryAPI interface {
|
||||||
// Query the latest events and state for a room from the room server.
|
// Query the latest events and state for a room from the room server.
|
||||||
|
|
@ -303,6 +328,19 @@ type RoomserverQueryAPI interface {
|
||||||
request *QueryBackfillRequest,
|
request *QueryBackfillRequest,
|
||||||
response *QueryBackfillResponse,
|
response *QueryBackfillResponse,
|
||||||
) error
|
) error
|
||||||
|
|
||||||
|
QueryServersInRoomAtEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
request *QueryServersInRoomAtEventRequest,
|
||||||
|
response *QueryServersInRoomAtEventResponse,
|
||||||
|
) error
|
||||||
|
|
||||||
|
// Asks for the default room version as preferred by the server.
|
||||||
|
QueryRoomVersionCapabilities(
|
||||||
|
ctx context.Context,
|
||||||
|
request *QueryRoomVersionCapabilitiesRequest,
|
||||||
|
response *QueryRoomVersionCapabilitiesResponse,
|
||||||
|
) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoomserverQueryLatestEventsAndStatePath is the HTTP path for the QueryLatestEventsAndState API.
|
// RoomserverQueryLatestEventsAndStatePath is the HTTP path for the QueryLatestEventsAndState API.
|
||||||
|
|
@ -332,8 +370,14 @@ const RoomserverQueryMissingEventsPath = "/api/roomserver/queryMissingEvents"
|
||||||
// RoomserverQueryStateAndAuthChainPath is the HTTP path for the QueryStateAndAuthChain API
|
// RoomserverQueryStateAndAuthChainPath is the HTTP path for the QueryStateAndAuthChain API
|
||||||
const RoomserverQueryStateAndAuthChainPath = "/api/roomserver/queryStateAndAuthChain"
|
const RoomserverQueryStateAndAuthChainPath = "/api/roomserver/queryStateAndAuthChain"
|
||||||
|
|
||||||
// RoomserverQueryBackfillPath is the HTTP path for the QueryMissingEvents API
|
// RoomserverQueryBackfillPath is the HTTP path for the QueryBackfillPath API
|
||||||
const RoomserverQueryBackfillPath = "/api/roomserver/QueryBackfill"
|
const RoomserverQueryBackfillPath = "/api/roomserver/queryBackfill"
|
||||||
|
|
||||||
|
// RoomserverQueryServersInRoomAtEventPath is the HTTP path for the QueryServersInRoomAtEvent API
|
||||||
|
const RoomserverQueryServersInRoomAtEventPath = "/api/roomserver/queryServersInRoomAtEvents"
|
||||||
|
|
||||||
|
// RoomserverQueryRoomVersionCapabilitiesPath is the HTTP path for the QueryRoomVersionCapabilities API
|
||||||
|
const RoomserverQueryRoomVersionCapabilitiesPath = "/api/roomserver/queryRoomVersionCapabilities"
|
||||||
|
|
||||||
// NewRoomserverQueryAPIHTTP creates a RoomserverQueryAPI implemented by talking to a HTTP POST API.
|
// NewRoomserverQueryAPIHTTP creates a RoomserverQueryAPI implemented by talking to a HTTP POST API.
|
||||||
// If httpClient is nil then it uses the http.DefaultClient
|
// If httpClient is nil then it uses the http.DefaultClient
|
||||||
|
|
@ -475,6 +519,32 @@ func (h *httpRoomserverQueryAPI) QueryBackfill(
|
||||||
span, ctx := opentracing.StartSpanFromContext(ctx, "QueryBackfill")
|
span, ctx := opentracing.StartSpanFromContext(ctx, "QueryBackfill")
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
|
|
||||||
apiURL := h.roomserverURL + RoomserverQueryMissingEventsPath
|
apiURL := h.roomserverURL + RoomserverQueryBackfillPath
|
||||||
|
return commonHTTP.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryServersInRoomAtEvent implements RoomServerQueryAPI
|
||||||
|
func (h *httpRoomserverQueryAPI) QueryServersInRoomAtEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
request *QueryServersInRoomAtEventRequest,
|
||||||
|
response *QueryServersInRoomAtEventResponse,
|
||||||
|
) error {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "QueryServersInRoomAtEvent")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
|
apiURL := h.roomserverURL + RoomserverQueryServersInRoomAtEventPath
|
||||||
|
return commonHTTP.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryServersInRoomAtEvent implements RoomServerQueryAPI
|
||||||
|
func (h *httpRoomserverQueryAPI) QueryRoomVersionCapabilities(
|
||||||
|
ctx context.Context,
|
||||||
|
request *QueryRoomVersionCapabilitiesRequest,
|
||||||
|
response *QueryRoomVersionCapabilitiesResponse,
|
||||||
|
) error {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "QueryRoomVersionCapabilities")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
|
apiURL := h.roomserverURL + RoomserverQueryRoomVersionCapabilitiesPath
|
||||||
return commonHTTP.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
|
return commonHTTP.PostJSON(ctx, span, h.httpClient, apiURL, request, response)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
// Copyright 2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -21,13 +23,14 @@ import (
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/roomserver/state"
|
"github.com/matrix-org/dendrite/roomserver/state"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/state/database"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A RoomEventDatabase has the storage APIs needed to store a room event.
|
// A RoomEventDatabase has the storage APIs needed to store a room event.
|
||||||
type RoomEventDatabase interface {
|
type RoomEventDatabase interface {
|
||||||
state.RoomStateDatabase
|
database.RoomStateDatabase
|
||||||
// Stores a matrix room event in the database
|
// Stores a matrix room event in the database
|
||||||
StoreEvent(
|
StoreEvent(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
|
@ -149,7 +152,12 @@ func calculateAndSetState(
|
||||||
stateAtEvent *types.StateAtEvent,
|
stateAtEvent *types.StateAtEvent,
|
||||||
event gomatrixserverlib.Event,
|
event gomatrixserverlib.Event,
|
||||||
) error {
|
) error {
|
||||||
var err error
|
// TODO: get the correct room version
|
||||||
|
roomState, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if input.HasState {
|
if input.HasState {
|
||||||
// We've been told what the state at the event is so we don't need to calculate it.
|
// We've been told what the state at the event is so we don't need to calculate it.
|
||||||
// Check that those state events are in the database and store the state.
|
// Check that those state events are in the database and store the state.
|
||||||
|
|
@ -163,7 +171,7 @@ func calculateAndSetState(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We haven't been told what the state at the event is so we need to calculate it from the prev_events
|
// We haven't been told what the state at the event is so we need to calculate it from the prev_events
|
||||||
if stateAtEvent.BeforeStateSnapshotNID, err = state.CalculateAndStoreStateBeforeEvent(ctx, db, event, roomNID); err != nil {
|
if stateAtEvent.BeforeStateSnapshotNID, err = roomState.CalculateAndStoreStateBeforeEvent(ctx, event, roomNID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
// Copyright 2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -171,27 +173,32 @@ func (u *latestEventsUpdater) doUpdateLatestEvents() error {
|
||||||
|
|
||||||
func (u *latestEventsUpdater) latestState() error {
|
func (u *latestEventsUpdater) latestState() error {
|
||||||
var err error
|
var err error
|
||||||
|
// TODO: get the correct room version
|
||||||
|
roomState, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, u.db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
latestStateAtEvents := make([]types.StateAtEvent, len(u.latest))
|
latestStateAtEvents := make([]types.StateAtEvent, len(u.latest))
|
||||||
for i := range u.latest {
|
for i := range u.latest {
|
||||||
latestStateAtEvents[i] = u.latest[i].StateAtEvent
|
latestStateAtEvents[i] = u.latest[i].StateAtEvent
|
||||||
}
|
}
|
||||||
u.newStateNID, err = state.CalculateAndStoreStateAfterEvents(
|
u.newStateNID, err = roomState.CalculateAndStoreStateAfterEvents(
|
||||||
u.ctx, u.db, u.roomNID, latestStateAtEvents,
|
u.ctx, u.roomNID, latestStateAtEvents,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
u.removed, u.added, err = state.DifferenceBetweeenStateSnapshots(
|
u.removed, u.added, err = roomState.DifferenceBetweeenStateSnapshots(
|
||||||
u.ctx, u.db, u.oldStateNID, u.newStateNID,
|
u.ctx, u.oldStateNID, u.newStateNID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
u.stateBeforeEventRemoves, u.stateBeforeEventAdds, err = state.DifferenceBetweeenStateSnapshots(
|
u.stateBeforeEventRemoves, u.stateBeforeEventAdds, err = roomState.DifferenceBetweeenStateSnapshots(
|
||||||
u.ctx, u.db, u.newStateNID, u.stateAtEvent.BeforeStateSnapshotNID,
|
u.ctx, u.newStateNID, u.stateAtEvent.BeforeStateSnapshotNID,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
// Copyright 2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -18,12 +20,15 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/roomserver/auth"
|
"github.com/matrix-org/dendrite/roomserver/auth"
|
||||||
"github.com/matrix-org/dendrite/roomserver/state"
|
"github.com/matrix-org/dendrite/roomserver/state"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/state/database"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/version"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
@ -39,7 +44,7 @@ type RoomserverQueryAPIEventDB interface {
|
||||||
|
|
||||||
// RoomserverQueryAPIDatabase has the storage APIs needed to implement the query API.
|
// RoomserverQueryAPIDatabase has the storage APIs needed to implement the query API.
|
||||||
type RoomserverQueryAPIDatabase interface {
|
type RoomserverQueryAPIDatabase interface {
|
||||||
state.RoomStateDatabase
|
database.RoomStateDatabase
|
||||||
RoomserverQueryAPIEventDB
|
RoomserverQueryAPIEventDB
|
||||||
// Look up the numeric ID for the room.
|
// Look up the numeric ID for the room.
|
||||||
// Returns 0 if the room doesn't exists.
|
// Returns 0 if the room doesn't exists.
|
||||||
|
|
@ -98,6 +103,11 @@ func (r *RoomserverQueryAPI) QueryLatestEventsAndState(
|
||||||
request *api.QueryLatestEventsAndStateRequest,
|
request *api.QueryLatestEventsAndStateRequest,
|
||||||
response *api.QueryLatestEventsAndStateResponse,
|
response *api.QueryLatestEventsAndStateResponse,
|
||||||
) error {
|
) error {
|
||||||
|
// TODO: get the correct room version
|
||||||
|
roomState, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, r.DB)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
response.QueryLatestEventsAndStateRequest = *request
|
response.QueryLatestEventsAndStateRequest = *request
|
||||||
roomNID, err := r.DB.RoomNID(ctx, request.RoomID)
|
roomNID, err := r.DB.RoomNID(ctx, request.RoomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -115,8 +125,8 @@ func (r *RoomserverQueryAPI) QueryLatestEventsAndState(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up the currrent state for the requested tuples.
|
// Look up the currrent state for the requested tuples.
|
||||||
stateEntries, err := state.LoadStateAtSnapshotForStringTuples(
|
stateEntries, err := roomState.LoadStateAtSnapshotForStringTuples(
|
||||||
ctx, r.DB, currentStateSnapshotNID, request.StateToFetch,
|
ctx, currentStateSnapshotNID, request.StateToFetch,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -137,6 +147,11 @@ func (r *RoomserverQueryAPI) QueryStateAfterEvents(
|
||||||
request *api.QueryStateAfterEventsRequest,
|
request *api.QueryStateAfterEventsRequest,
|
||||||
response *api.QueryStateAfterEventsResponse,
|
response *api.QueryStateAfterEventsResponse,
|
||||||
) error {
|
) error {
|
||||||
|
// TODO: get the correct room version
|
||||||
|
roomState, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, r.DB)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
response.QueryStateAfterEventsRequest = *request
|
response.QueryStateAfterEventsRequest = *request
|
||||||
roomNID, err := r.DB.RoomNID(ctx, request.RoomID)
|
roomNID, err := r.DB.RoomNID(ctx, request.RoomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -159,8 +174,8 @@ func (r *RoomserverQueryAPI) QueryStateAfterEvents(
|
||||||
response.PrevEventsExist = true
|
response.PrevEventsExist = true
|
||||||
|
|
||||||
// Look up the currrent state for the requested tuples.
|
// Look up the currrent state for the requested tuples.
|
||||||
stateEntries, err := state.LoadStateAfterEventsForStringTuples(
|
stateEntries, err := roomState.LoadStateAfterEventsForStringTuples(
|
||||||
ctx, r.DB, prevStates, request.StateToFetch,
|
ctx, prevStates, request.StateToFetch,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -315,6 +330,11 @@ func (r *RoomserverQueryAPI) QueryMembershipsForRoom(
|
||||||
func (r *RoomserverQueryAPI) getMembershipsBeforeEventNID(
|
func (r *RoomserverQueryAPI) getMembershipsBeforeEventNID(
|
||||||
ctx context.Context, eventNID types.EventNID, joinedOnly bool,
|
ctx context.Context, eventNID types.EventNID, joinedOnly bool,
|
||||||
) ([]types.Event, error) {
|
) ([]types.Event, error) {
|
||||||
|
// TODO: get the correct room version
|
||||||
|
roomState, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, r.DB)
|
||||||
|
if err != nil {
|
||||||
|
return []types.Event{}, err
|
||||||
|
}
|
||||||
events := []types.Event{}
|
events := []types.Event{}
|
||||||
// Lookup the event NID
|
// Lookup the event NID
|
||||||
eIDs, err := r.DB.EventIDs(ctx, []types.EventNID{eventNID})
|
eIDs, err := r.DB.EventIDs(ctx, []types.EventNID{eventNID})
|
||||||
|
|
@ -329,7 +349,7 @@ func (r *RoomserverQueryAPI) getMembershipsBeforeEventNID(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the state as it was when this event was fired
|
// Fetch the state as it was when this event was fired
|
||||||
stateEntries, err := state.LoadCombinedStateAfterEvents(ctx, r.DB, prevState)
|
stateEntries, err := roomState.LoadCombinedStateAfterEvents(ctx, prevState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -416,7 +436,13 @@ func (r *RoomserverQueryAPI) QueryServerAllowedToSeeEvent(
|
||||||
func (r *RoomserverQueryAPI) checkServerAllowedToSeeEvent(
|
func (r *RoomserverQueryAPI) checkServerAllowedToSeeEvent(
|
||||||
ctx context.Context, eventID string, serverName gomatrixserverlib.ServerName,
|
ctx context.Context, eventID string, serverName gomatrixserverlib.ServerName,
|
||||||
) (bool, error) {
|
) (bool, error) {
|
||||||
stateEntries, err := state.LoadStateAtEvent(ctx, r.DB, eventID)
|
// TODO: get the correct room version
|
||||||
|
roomState, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, r.DB)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stateEntries, err := roomState.LoadStateAtEvent(ctx, eventID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
@ -570,6 +596,12 @@ func (r *RoomserverQueryAPI) QueryStateAndAuthChain(
|
||||||
request *api.QueryStateAndAuthChainRequest,
|
request *api.QueryStateAndAuthChainRequest,
|
||||||
response *api.QueryStateAndAuthChainResponse,
|
response *api.QueryStateAndAuthChainResponse,
|
||||||
) error {
|
) error {
|
||||||
|
// TODO: get the correct room version
|
||||||
|
roomState, err := state.GetStateResolutionAlgorithm(state.StateResolutionAlgorithmV1, r.DB)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
response.QueryStateAndAuthChainRequest = *request
|
response.QueryStateAndAuthChainRequest = *request
|
||||||
roomNID, err := r.DB.RoomNID(ctx, request.RoomID)
|
roomNID, err := r.DB.RoomNID(ctx, request.RoomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -592,8 +624,8 @@ func (r *RoomserverQueryAPI) QueryStateAndAuthChain(
|
||||||
response.PrevEventsExist = true
|
response.PrevEventsExist = true
|
||||||
|
|
||||||
// Look up the currrent state for the requested tuples.
|
// Look up the currrent state for the requested tuples.
|
||||||
stateEntries, err := state.LoadCombinedStateAfterEvents(
|
stateEntries, err := roomState.LoadCombinedStateAfterEvents(
|
||||||
ctx, r.DB, prevStates,
|
ctx, prevStates,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -609,53 +641,111 @@ func (r *RoomserverQueryAPI) QueryStateAndAuthChain(
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// getAuthChain fetches the auth chain for the given auth events.
|
// getAuthChain fetches the auth chain for the given auth events. An auth chain
|
||||||
// An auth chain is the list of all events that are referenced in the
|
// is the list of all events that are referenced in the auth_events section, and
|
||||||
// auth_events section, and all their auth_events, recursively.
|
// all their auth_events, recursively. The returned set of events contain the
|
||||||
// The returned set of events contain the given events.
|
// given events. Will *not* error if we don't have all auth events.
|
||||||
// Will *not* error if we don't have all auth events.
|
|
||||||
func getAuthChain(
|
func getAuthChain(
|
||||||
ctx context.Context, dB RoomserverQueryAPIEventDB, authEventIDs []string,
|
ctx context.Context, dB RoomserverQueryAPIEventDB, authEventIDs []string,
|
||||||
) ([]gomatrixserverlib.Event, error) {
|
) ([]gomatrixserverlib.Event, error) {
|
||||||
var authEvents []gomatrixserverlib.Event
|
// List of event IDs to fetch. On each pass, these events will be requested
|
||||||
|
// from the database and the `eventsToFetch` will be updated with any new
|
||||||
// List of event ids to fetch. These will be added to the result and
|
// events that we have learned about and need to find. When `eventsToFetch`
|
||||||
// their auth events will be fetched (if they haven't been previously)
|
// is eventually empty, we should have reached the end of the chain.
|
||||||
eventsToFetch := authEventIDs
|
eventsToFetch := authEventIDs
|
||||||
|
authEventsMap := make(map[string]gomatrixserverlib.Event)
|
||||||
|
|
||||||
// Set of events we've already fetched.
|
|
||||||
fetchedEventMap := make(map[string]bool)
|
|
||||||
|
|
||||||
// Check if there's anything left to do
|
|
||||||
for len(eventsToFetch) > 0 {
|
for len(eventsToFetch) > 0 {
|
||||||
// Convert eventIDs to events. First need to fetch NIDs
|
// Try to retrieve the events from the database.
|
||||||
events, err := dB.EventsFromIDs(ctx, eventsToFetch)
|
events, err := dB.EventsFromIDs(ctx, eventsToFetch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Work out a) which events we should add to the returned list of
|
// We've now fetched these events so clear out `eventsToFetch`. Soon we may
|
||||||
// events and b) which of the auth events we haven't seen yet and
|
// add newly discovered events to this for the next pass.
|
||||||
// add them to the list of events to fetch.
|
|
||||||
eventsToFetch = eventsToFetch[:0]
|
eventsToFetch = eventsToFetch[:0]
|
||||||
for _, event := range events {
|
|
||||||
fetchedEventMap[event.EventID()] = true
|
|
||||||
authEvents = append(authEvents, event.Event)
|
|
||||||
|
|
||||||
// Now we need to fetch any auth events that we haven't
|
for _, event := range events {
|
||||||
// previously seen.
|
// Store the event in the event map - this prevents us from requesting it
|
||||||
for _, authEventID := range event.AuthEventIDs() {
|
// from the database again.
|
||||||
if !fetchedEventMap[authEventID] {
|
authEventsMap[event.EventID()] = event.Event
|
||||||
fetchedEventMap[authEventID] = true
|
|
||||||
eventsToFetch = append(eventsToFetch, authEventID)
|
// Extract all of the auth events from the newly obtained event. If we
|
||||||
|
// don't already have a record of the event, record it in the list of
|
||||||
|
// events we want to request for the next pass.
|
||||||
|
for _, authEvent := range event.AuthEvents() {
|
||||||
|
if _, ok := authEventsMap[authEvent.EventID]; !ok {
|
||||||
|
eventsToFetch = append(eventsToFetch, authEvent.EventID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We've now retrieved all of the events we can. Flatten them down into an
|
||||||
|
// array and return them.
|
||||||
|
var authEvents []gomatrixserverlib.Event
|
||||||
|
for _, event := range authEventsMap {
|
||||||
|
authEvents = append(authEvents, event)
|
||||||
|
}
|
||||||
|
|
||||||
return authEvents, nil
|
return authEvents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryServersInRoomAtEvent implements api.RoomserverQueryAPI
|
||||||
|
func (r *RoomserverQueryAPI) QueryServersInRoomAtEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
request *api.QueryServersInRoomAtEventRequest,
|
||||||
|
response *api.QueryServersInRoomAtEventResponse,
|
||||||
|
) error {
|
||||||
|
// getMembershipsBeforeEventNID requires a NID, so retrieving the NID for
|
||||||
|
// the event is necessary.
|
||||||
|
NIDs, err := r.DB.EventNIDs(ctx, []string{request.EventID})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve all "m.room.member" state events of "join" membership, which
|
||||||
|
// contains the list of users in the room before the event, therefore all
|
||||||
|
// the servers in it at that moment.
|
||||||
|
events, err := r.getMembershipsBeforeEventNID(ctx, NIDs[request.EventID], true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the server names in a temporary map to avoid duplicates.
|
||||||
|
servers := make(map[gomatrixserverlib.ServerName]bool)
|
||||||
|
for _, event := range events {
|
||||||
|
servers[event.Origin()] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the response.
|
||||||
|
for server := range servers {
|
||||||
|
response.Servers = append(response.Servers, server)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRoomVersionCapabilities implements api.RoomserverQueryAPI
|
||||||
|
func (r *RoomserverQueryAPI) QueryRoomVersionCapabilities(
|
||||||
|
ctx context.Context,
|
||||||
|
request *api.QueryRoomVersionCapabilitiesRequest,
|
||||||
|
response *api.QueryRoomVersionCapabilitiesResponse,
|
||||||
|
) error {
|
||||||
|
response.DefaultRoomVersion = strconv.Itoa(int(version.GetDefaultRoomVersion()))
|
||||||
|
response.AvailableRoomVersions = make(map[string]string)
|
||||||
|
for v, desc := range version.GetSupportedRoomVersions() {
|
||||||
|
sv := strconv.Itoa(int(v))
|
||||||
|
if desc.Stable {
|
||||||
|
response.AvailableRoomVersions[sv] = "stable"
|
||||||
|
} else {
|
||||||
|
response.AvailableRoomVersions[sv] = "unstable"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetupHTTP adds the RoomserverQueryAPI handlers to the http.ServeMux.
|
// SetupHTTP adds the RoomserverQueryAPI handlers to the http.ServeMux.
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) {
|
func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) {
|
||||||
|
|
@ -799,4 +889,32 @@ func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) {
|
||||||
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
servMux.Handle(
|
||||||
|
api.RoomserverQueryServersInRoomAtEventPath,
|
||||||
|
common.MakeInternalAPI("QueryServersInRoomAtEvent", func(req *http.Request) util.JSONResponse {
|
||||||
|
var request api.QueryServersInRoomAtEventRequest
|
||||||
|
var response api.QueryServersInRoomAtEventResponse
|
||||||
|
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
if err := r.QueryServersInRoomAtEvent(req.Context(), &request, &response); err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
servMux.Handle(
|
||||||
|
api.RoomserverQueryRoomVersionCapabilitiesPath,
|
||||||
|
common.MakeInternalAPI("QueryRoomVersionCapabilities", func(req *http.Request) util.JSONResponse {
|
||||||
|
var request api.QueryRoomVersionCapabilitiesRequest
|
||||||
|
var response api.QueryRoomVersionCapabilitiesResponse
|
||||||
|
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
if err := r.QueryRoomVersionCapabilities(req.Context(), &request, &response); err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
64
roomserver/state/database/database.go
Normal file
64
roomserver/state/database/database.go
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
// Copyright 2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A RoomStateDatabase has the storage APIs needed to load state from the database
|
||||||
|
type RoomStateDatabase interface {
|
||||||
|
// Store the room state at an event in the database
|
||||||
|
AddState(
|
||||||
|
ctx context.Context,
|
||||||
|
roomNID types.RoomNID,
|
||||||
|
stateBlockNIDs []types.StateBlockNID,
|
||||||
|
state []types.StateEntry,
|
||||||
|
) (types.StateSnapshotNID, error)
|
||||||
|
// Look up the state of a room at each event for a list of string event IDs.
|
||||||
|
// Returns an error if there is an error talking to the database
|
||||||
|
// Returns a types.MissingEventError if the room state for the event IDs aren't in the database
|
||||||
|
StateAtEventIDs(ctx context.Context, eventIDs []string) ([]types.StateAtEvent, error)
|
||||||
|
// Look up the numeric IDs for a list of string event types.
|
||||||
|
// Returns a map from string event type to numeric ID for the event type.
|
||||||
|
EventTypeNIDs(ctx context.Context, eventTypes []string) (map[string]types.EventTypeNID, error)
|
||||||
|
// Look up the numeric IDs for a list of string event state keys.
|
||||||
|
// Returns a map from string state key to numeric ID for the state key.
|
||||||
|
EventStateKeyNIDs(ctx context.Context, eventStateKeys []string) (map[string]types.EventStateKeyNID, error)
|
||||||
|
// Look up the numeric state data IDs for each numeric state snapshot ID
|
||||||
|
// The returned slice is sorted by numeric state snapshot ID.
|
||||||
|
StateBlockNIDs(ctx context.Context, stateNIDs []types.StateSnapshotNID) ([]types.StateBlockNIDList, error)
|
||||||
|
// Look up the state data for each numeric state data ID
|
||||||
|
// The returned slice is sorted by numeric state data ID.
|
||||||
|
StateEntries(ctx context.Context, stateBlockNIDs []types.StateBlockNID) ([]types.StateEntryList, error)
|
||||||
|
// Look up the state data for the state key tuples for each numeric state block ID
|
||||||
|
// This is used to fetch a subset of the room state at a snapshot.
|
||||||
|
// If a block doesn't contain any of the requested tuples then it can be discarded from the result.
|
||||||
|
// The returned slice is sorted by numeric state block ID.
|
||||||
|
StateEntriesForTuples(
|
||||||
|
ctx context.Context,
|
||||||
|
stateBlockNIDs []types.StateBlockNID,
|
||||||
|
stateKeyTuples []types.StateKeyTuple,
|
||||||
|
) ([]types.StateEntryList, error)
|
||||||
|
// Look up the Events for a list of numeric event IDs.
|
||||||
|
// Returns a sorted list of events.
|
||||||
|
Events(ctx context.Context, eventNIDs []types.EventNID) ([]types.Event, error)
|
||||||
|
// Look up snapshot NID for an event ID string
|
||||||
|
SnapshotNIDFromEventID(ctx context.Context, eventID string) (types.StateSnapshotNID, error)
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
927
roomserver/state/v1/state.go
Normal file
927
roomserver/state/v1/state.go
Normal file
|
|
@ -0,0 +1,927 @@
|
||||||
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package state provides functions for reading state from the database.
|
||||||
|
// The functions for writing state to the database are the input package.
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/state/database"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StateResolutionV1 struct {
|
||||||
|
db database.RoomStateDatabase
|
||||||
|
}
|
||||||
|
|
||||||
|
func Prepare(db database.RoomStateDatabase) StateResolutionV1 {
|
||||||
|
return StateResolutionV1{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadStateAtSnapshot loads the full state of a room at a particular snapshot.
|
||||||
|
// This is typically the state before an event or the current state of a room.
|
||||||
|
// Returns a sorted list of state entries or an error if there was a problem talking to the database.
|
||||||
|
func (v StateResolutionV1) LoadStateAtSnapshot(
|
||||||
|
ctx context.Context, stateNID types.StateSnapshotNID,
|
||||||
|
) ([]types.StateEntry, error) {
|
||||||
|
stateBlockNIDLists, err := v.db.StateBlockNIDs(ctx, []types.StateSnapshotNID{stateNID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// We've asked for exactly one snapshot from the db so we should have exactly one entry in the result.
|
||||||
|
stateBlockNIDList := stateBlockNIDLists[0]
|
||||||
|
|
||||||
|
stateEntryLists, err := v.db.StateEntries(ctx, stateBlockNIDList.StateBlockNIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stateEntriesMap := stateEntryListMap(stateEntryLists)
|
||||||
|
|
||||||
|
// Combine all the state entries for this snapshot.
|
||||||
|
// The order of state block NIDs in the list tells us the order to combine them in.
|
||||||
|
var fullState []types.StateEntry
|
||||||
|
for _, stateBlockNID := range stateBlockNIDList.StateBlockNIDs {
|
||||||
|
entries, ok := stateEntriesMap.lookup(stateBlockNID)
|
||||||
|
if !ok {
|
||||||
|
// This should only get hit if the database is corrupt.
|
||||||
|
// It should be impossible for an event to reference a NID that doesn't exist
|
||||||
|
panic(fmt.Errorf("Corrupt DB: Missing state block numeric ID %d", stateBlockNID))
|
||||||
|
}
|
||||||
|
fullState = append(fullState, entries...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stable sort so that the most recent entry for each state key stays
|
||||||
|
// remains later in the list than the older entries for the same state key.
|
||||||
|
sort.Stable(stateEntryByStateKeySorter(fullState))
|
||||||
|
// Unique returns the last entry and hence the most recent entry for each state key.
|
||||||
|
fullState = fullState[:util.Unique(stateEntryByStateKeySorter(fullState))]
|
||||||
|
return fullState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadStateAtEvent loads the full state of a room at a particular event.
|
||||||
|
func (v StateResolutionV1) LoadStateAtEvent(
|
||||||
|
ctx context.Context, eventID string,
|
||||||
|
) ([]types.StateEntry, error) {
|
||||||
|
snapshotNID, err := v.db.SnapshotNIDFromEventID(ctx, eventID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stateEntries, err := v.LoadStateAtSnapshot(ctx, snapshotNID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return stateEntries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadCombinedStateAfterEvents loads a snapshot of the state after each of the events
|
||||||
|
// and combines those snapshots together into a single list.
|
||||||
|
func (v StateResolutionV1) LoadCombinedStateAfterEvents(
|
||||||
|
ctx context.Context, prevStates []types.StateAtEvent,
|
||||||
|
) ([]types.StateEntry, error) {
|
||||||
|
stateNIDs := make([]types.StateSnapshotNID, len(prevStates))
|
||||||
|
for i, state := range prevStates {
|
||||||
|
stateNIDs[i] = state.BeforeStateSnapshotNID
|
||||||
|
}
|
||||||
|
// Fetch the state snapshots for the state before the each prev event from the database.
|
||||||
|
// Deduplicate the IDs before passing them to the database.
|
||||||
|
// There could be duplicates because the events could be state events where
|
||||||
|
// the snapshot of the room state before them was the same.
|
||||||
|
stateBlockNIDLists, err := v.db.StateBlockNIDs(ctx, uniqueStateSnapshotNIDs(stateNIDs))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var stateBlockNIDs []types.StateBlockNID
|
||||||
|
for _, list := range stateBlockNIDLists {
|
||||||
|
stateBlockNIDs = append(stateBlockNIDs, list.StateBlockNIDs...)
|
||||||
|
}
|
||||||
|
// Fetch the state entries that will be combined to create the snapshots.
|
||||||
|
// Deduplicate the IDs before passing them to the database.
|
||||||
|
// There could be duplicates because a block of state entries could be reused by
|
||||||
|
// multiple snapshots.
|
||||||
|
stateEntryLists, err := v.db.StateEntries(ctx, uniqueStateBlockNIDs(stateBlockNIDs))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stateBlockNIDsMap := stateBlockNIDListMap(stateBlockNIDLists)
|
||||||
|
stateEntriesMap := stateEntryListMap(stateEntryLists)
|
||||||
|
|
||||||
|
// Combine the entries from all the snapshots of state after each prev event into a single list.
|
||||||
|
var combined []types.StateEntry
|
||||||
|
for _, prevState := range prevStates {
|
||||||
|
// Grab the list of state data NIDs for this snapshot.
|
||||||
|
stateBlockNIDs, ok := stateBlockNIDsMap.lookup(prevState.BeforeStateSnapshotNID)
|
||||||
|
if !ok {
|
||||||
|
// This should only get hit if the database is corrupt.
|
||||||
|
// It should be impossible for an event to reference a NID that doesn't exist
|
||||||
|
panic(fmt.Errorf("Corrupt DB: Missing state snapshot numeric ID %d", prevState.BeforeStateSnapshotNID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine all the state entries for this snapshot.
|
||||||
|
// The order of state block NIDs in the list tells us the order to combine them in.
|
||||||
|
var fullState []types.StateEntry
|
||||||
|
for _, stateBlockNID := range stateBlockNIDs {
|
||||||
|
entries, ok := stateEntriesMap.lookup(stateBlockNID)
|
||||||
|
if !ok {
|
||||||
|
// This should only get hit if the database is corrupt.
|
||||||
|
// It should be impossible for an event to reference a NID that doesn't exist
|
||||||
|
panic(fmt.Errorf("Corrupt DB: Missing state block numeric ID %d", stateBlockNID))
|
||||||
|
}
|
||||||
|
fullState = append(fullState, entries...)
|
||||||
|
}
|
||||||
|
if prevState.IsStateEvent() {
|
||||||
|
// If the prev event was a state event then add an entry for the event itself
|
||||||
|
// so that we get the state after the event rather than the state before.
|
||||||
|
fullState = append(fullState, prevState.StateEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stable sort so that the most recent entry for each state key stays
|
||||||
|
// remains later in the list than the older entries for the same state key.
|
||||||
|
sort.Stable(stateEntryByStateKeySorter(fullState))
|
||||||
|
// Unique returns the last entry and hence the most recent entry for each state key.
|
||||||
|
fullState = fullState[:util.Unique(stateEntryByStateKeySorter(fullState))]
|
||||||
|
// Add the full state for this StateSnapshotNID.
|
||||||
|
combined = append(combined, fullState...)
|
||||||
|
}
|
||||||
|
return combined, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DifferenceBetweeenStateSnapshots works out which state entries have been added and removed between two snapshots.
|
||||||
|
func (v StateResolutionV1) DifferenceBetweeenStateSnapshots(
|
||||||
|
ctx context.Context, oldStateNID, newStateNID types.StateSnapshotNID,
|
||||||
|
) (removed, added []types.StateEntry, err error) {
|
||||||
|
if oldStateNID == newStateNID {
|
||||||
|
// If the snapshot NIDs are the same then nothing has changed
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldEntries []types.StateEntry
|
||||||
|
var newEntries []types.StateEntry
|
||||||
|
if oldStateNID != 0 {
|
||||||
|
oldEntries, err = v.LoadStateAtSnapshot(ctx, oldStateNID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if newStateNID != 0 {
|
||||||
|
newEntries, err = v.LoadStateAtSnapshot(ctx, newStateNID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldI int
|
||||||
|
var newI int
|
||||||
|
for {
|
||||||
|
switch {
|
||||||
|
case oldI == len(oldEntries):
|
||||||
|
// We've reached the end of the old entries.
|
||||||
|
// The rest of the new list must have been newly added.
|
||||||
|
added = append(added, newEntries[newI:]...)
|
||||||
|
return
|
||||||
|
case newI == len(newEntries):
|
||||||
|
// We've reached the end of the new entries.
|
||||||
|
// The rest of the old list must be have been removed.
|
||||||
|
removed = append(removed, oldEntries[oldI:]...)
|
||||||
|
return
|
||||||
|
case oldEntries[oldI] == newEntries[newI]:
|
||||||
|
// The entry is in both lists so skip over it.
|
||||||
|
oldI++
|
||||||
|
newI++
|
||||||
|
case oldEntries[oldI].LessThan(newEntries[newI]):
|
||||||
|
// The lists are sorted so the old entry being less than the new entry means that it only appears in the old list.
|
||||||
|
removed = append(removed, oldEntries[oldI])
|
||||||
|
oldI++
|
||||||
|
default:
|
||||||
|
// Reaching the default case implies that the new entry is less than the old entry.
|
||||||
|
// Since the lists are sorted this means that it only appears in the new list.
|
||||||
|
added = append(added, newEntries[newI])
|
||||||
|
newI++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadStateAtSnapshotForStringTuples loads the state for a list of event type and state key pairs at a snapshot.
|
||||||
|
// This is used when we only want to load a subset of the room state at a snapshot.
|
||||||
|
// If there is no entry for a given event type and state key pair then it will be discarded.
|
||||||
|
// This is typically the state before an event or the current state of a room.
|
||||||
|
// Returns a sorted list of state entries or an error if there was a problem talking to the database.
|
||||||
|
func (v StateResolutionV1) LoadStateAtSnapshotForStringTuples(
|
||||||
|
ctx context.Context,
|
||||||
|
stateNID types.StateSnapshotNID,
|
||||||
|
stateKeyTuples []gomatrixserverlib.StateKeyTuple,
|
||||||
|
) ([]types.StateEntry, error) {
|
||||||
|
numericTuples, err := v.stringTuplesToNumericTuples(ctx, stateKeyTuples)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return v.loadStateAtSnapshotForNumericTuples(ctx, stateNID, numericTuples)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringTuplesToNumericTuples converts the string state key tuples into numeric IDs
|
||||||
|
// If there isn't a numeric ID for either the event type or the event state key then the tuple is discarded.
|
||||||
|
// Returns an error if there was a problem talking to the database.
|
||||||
|
func (v StateResolutionV1) stringTuplesToNumericTuples(
|
||||||
|
ctx context.Context,
|
||||||
|
stringTuples []gomatrixserverlib.StateKeyTuple,
|
||||||
|
) ([]types.StateKeyTuple, error) {
|
||||||
|
eventTypes := make([]string, len(stringTuples))
|
||||||
|
stateKeys := make([]string, len(stringTuples))
|
||||||
|
for i := range stringTuples {
|
||||||
|
eventTypes[i] = stringTuples[i].EventType
|
||||||
|
stateKeys[i] = stringTuples[i].StateKey
|
||||||
|
}
|
||||||
|
eventTypes = util.UniqueStrings(eventTypes)
|
||||||
|
eventTypeMap, err := v.db.EventTypeNIDs(ctx, eventTypes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stateKeys = util.UniqueStrings(stateKeys)
|
||||||
|
stateKeyMap, err := v.db.EventStateKeyNIDs(ctx, stateKeys)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []types.StateKeyTuple
|
||||||
|
for _, stringTuple := range stringTuples {
|
||||||
|
var numericTuple types.StateKeyTuple
|
||||||
|
var ok1, ok2 bool
|
||||||
|
numericTuple.EventTypeNID, ok1 = eventTypeMap[stringTuple.EventType]
|
||||||
|
numericTuple.EventStateKeyNID, ok2 = stateKeyMap[stringTuple.StateKey]
|
||||||
|
// Discard the tuple if there wasn't a numeric ID for either the event type or the state key.
|
||||||
|
if ok1 && ok2 {
|
||||||
|
result = append(result, numericTuple)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadStateAtSnapshotForNumericTuples loads the state for a list of event type and state key pairs at a snapshot.
|
||||||
|
// This is used when we only want to load a subset of the room state at a snapshot.
|
||||||
|
// If there is no entry for a given event type and state key pair then it will be discarded.
|
||||||
|
// This is typically the state before an event or the current state of a room.
|
||||||
|
// Returns a sorted list of state entries or an error if there was a problem talking to the database.
|
||||||
|
func (v StateResolutionV1) loadStateAtSnapshotForNumericTuples(
|
||||||
|
ctx context.Context,
|
||||||
|
stateNID types.StateSnapshotNID,
|
||||||
|
stateKeyTuples []types.StateKeyTuple,
|
||||||
|
) ([]types.StateEntry, error) {
|
||||||
|
stateBlockNIDLists, err := v.db.StateBlockNIDs(ctx, []types.StateSnapshotNID{stateNID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// We've asked for exactly one snapshot from the db so we should have exactly one entry in the result.
|
||||||
|
stateBlockNIDList := stateBlockNIDLists[0]
|
||||||
|
|
||||||
|
stateEntryLists, err := v.db.StateEntriesForTuples(
|
||||||
|
ctx, stateBlockNIDList.StateBlockNIDs, stateKeyTuples,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stateEntriesMap := stateEntryListMap(stateEntryLists)
|
||||||
|
|
||||||
|
// Combine all the state entries for this snapshot.
|
||||||
|
// The order of state block NIDs in the list tells us the order to combine them in.
|
||||||
|
var fullState []types.StateEntry
|
||||||
|
for _, stateBlockNID := range stateBlockNIDList.StateBlockNIDs {
|
||||||
|
entries, ok := stateEntriesMap.lookup(stateBlockNID)
|
||||||
|
if !ok {
|
||||||
|
// If the block is missing from the map it means that none of its entries matched a requested tuple.
|
||||||
|
// This can happen if the block doesn't contain an update for one of the requested tuples.
|
||||||
|
// If none of the requested tuples are in the block then it can be safely skipped.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fullState = append(fullState, entries...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stable sort so that the most recent entry for each state key stays
|
||||||
|
// remains later in the list than the older entries for the same state key.
|
||||||
|
sort.Stable(stateEntryByStateKeySorter(fullState))
|
||||||
|
// Unique returns the last entry and hence the most recent entry for each state key.
|
||||||
|
fullState = fullState[:util.Unique(stateEntryByStateKeySorter(fullState))]
|
||||||
|
return fullState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadStateAfterEventsForStringTuples loads the state for a list of event type
|
||||||
|
// and state key pairs after list of events.
|
||||||
|
// This is used when we only want to load a subset of the room state after a list of events.
|
||||||
|
// If there is no entry for a given event type and state key pair then it will be discarded.
|
||||||
|
// This is typically the state before an event.
|
||||||
|
// Returns a sorted list of state entries or an error if there was a problem talking to the database.
|
||||||
|
func (v StateResolutionV1) LoadStateAfterEventsForStringTuples(
|
||||||
|
ctx context.Context,
|
||||||
|
prevStates []types.StateAtEvent,
|
||||||
|
stateKeyTuples []gomatrixserverlib.StateKeyTuple,
|
||||||
|
) ([]types.StateEntry, error) {
|
||||||
|
numericTuples, err := v.stringTuplesToNumericTuples(ctx, stateKeyTuples)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return v.loadStateAfterEventsForNumericTuples(ctx, prevStates, numericTuples)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v StateResolutionV1) loadStateAfterEventsForNumericTuples(
|
||||||
|
ctx context.Context,
|
||||||
|
prevStates []types.StateAtEvent,
|
||||||
|
stateKeyTuples []types.StateKeyTuple,
|
||||||
|
) ([]types.StateEntry, error) {
|
||||||
|
if len(prevStates) == 1 {
|
||||||
|
// Fast path for a single event.
|
||||||
|
prevState := prevStates[0]
|
||||||
|
result, err := v.loadStateAtSnapshotForNumericTuples(
|
||||||
|
ctx, prevState.BeforeStateSnapshotNID, stateKeyTuples,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if prevState.IsStateEvent() {
|
||||||
|
// The result is current the state before the requested event.
|
||||||
|
// We want the state after the requested event.
|
||||||
|
// If the requested event was a state event then we need to
|
||||||
|
// update that key in the result.
|
||||||
|
// If the requested event wasn't a state event then the state after
|
||||||
|
// it is the same as the state before it.
|
||||||
|
for i := range result {
|
||||||
|
if result[i].StateKeyTuple == prevState.StateKeyTuple {
|
||||||
|
result[i] = prevState.StateEntry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slow path for more that one event.
|
||||||
|
// Load the entire state so that we can do conflict resolution if we need to.
|
||||||
|
// TODO: The are some optimistations we could do here:
|
||||||
|
// 1) We only need to do conflict resolution if there is a conflict in the
|
||||||
|
// requested tuples so we might try loading just those tuples and then
|
||||||
|
// checking for conflicts.
|
||||||
|
// 2) When there is a conflict we still only need to load the state
|
||||||
|
// needed to do conflict resolution which would save us having to load
|
||||||
|
// the full state.
|
||||||
|
|
||||||
|
// TODO: Add metrics for this as it could take a long time for big rooms
|
||||||
|
// with large conflicts.
|
||||||
|
fullState, _, _, err := v.calculateStateAfterManyEvents(ctx, prevStates)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the full state so we can use it as a map.
|
||||||
|
sort.Sort(stateEntrySorter(fullState))
|
||||||
|
|
||||||
|
// Filter the full state down to the required tuples.
|
||||||
|
var result []types.StateEntry
|
||||||
|
for _, tuple := range stateKeyTuples {
|
||||||
|
eventNID, ok := stateEntryMap(fullState).lookup(tuple)
|
||||||
|
if ok {
|
||||||
|
result = append(result, types.StateEntry{
|
||||||
|
StateKeyTuple: tuple,
|
||||||
|
EventNID: eventNID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Sort(stateEntrySorter(result))
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var calculateStateDurations = prometheus.NewSummaryVec(
|
||||||
|
prometheus.SummaryOpts{
|
||||||
|
Namespace: "dendrite",
|
||||||
|
Subsystem: "roomserver",
|
||||||
|
Name: "calculate_state_duration_microseconds",
|
||||||
|
Help: "How long it takes to calculate the state after a list of events",
|
||||||
|
},
|
||||||
|
// Takes two labels:
|
||||||
|
// algorithm:
|
||||||
|
// The algorithm used to calculate the state or the step it failed on if it failed.
|
||||||
|
// Labels starting with "_" are used to indicate when the algorithm fails halfway.
|
||||||
|
// outcome:
|
||||||
|
// Whether the state was successfully calculated.
|
||||||
|
//
|
||||||
|
// The possible values for algorithm are:
|
||||||
|
// empty_state -> The list of events was empty so the state is empty.
|
||||||
|
// no_change -> The state hasn't changed.
|
||||||
|
// single_delta -> There was a single event added to the state in a way that can be encoded as a single delta
|
||||||
|
// full_state_no_conflicts -> We created a new copy of the full room state, but didn't enounter any conflicts
|
||||||
|
// while doing so.
|
||||||
|
// full_state_with_conflicts -> We created a new copy of the full room state and had to resolve conflicts to do so.
|
||||||
|
// _load_state_block_nids -> Failed loading the state block nids for a single previous state.
|
||||||
|
// _load_combined_state -> Failed to load the combined state.
|
||||||
|
// _resolve_conflicts -> Failed to resolve conflicts.
|
||||||
|
[]string{"algorithm", "outcome"},
|
||||||
|
)
|
||||||
|
|
||||||
|
var calculateStatePrevEventLength = prometheus.NewSummaryVec(
|
||||||
|
prometheus.SummaryOpts{
|
||||||
|
Namespace: "dendrite",
|
||||||
|
Subsystem: "roomserver",
|
||||||
|
Name: "calculate_state_prev_event_length",
|
||||||
|
Help: "The length of the list of events to calculate the state after",
|
||||||
|
},
|
||||||
|
[]string{"algorithm", "outcome"},
|
||||||
|
)
|
||||||
|
|
||||||
|
var calculateStateFullStateLength = prometheus.NewSummaryVec(
|
||||||
|
prometheus.SummaryOpts{
|
||||||
|
Namespace: "dendrite",
|
||||||
|
Subsystem: "roomserver",
|
||||||
|
Name: "calculate_state_full_state_length",
|
||||||
|
Help: "The length of the full room state.",
|
||||||
|
},
|
||||||
|
[]string{"algorithm", "outcome"},
|
||||||
|
)
|
||||||
|
|
||||||
|
var calculateStateConflictLength = prometheus.NewSummaryVec(
|
||||||
|
prometheus.SummaryOpts{
|
||||||
|
Namespace: "dendrite",
|
||||||
|
Subsystem: "roomserver",
|
||||||
|
Name: "calculate_state_conflict_state_length",
|
||||||
|
Help: "The length of the conflicted room state.",
|
||||||
|
},
|
||||||
|
[]string{"algorithm", "outcome"},
|
||||||
|
)
|
||||||
|
|
||||||
|
type calculateStateMetrics struct {
|
||||||
|
algorithm string
|
||||||
|
startTime time.Time
|
||||||
|
prevEventLength int
|
||||||
|
fullStateLength int
|
||||||
|
conflictLength int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *calculateStateMetrics) stop(stateNID types.StateSnapshotNID, err error) (types.StateSnapshotNID, error) {
|
||||||
|
var outcome string
|
||||||
|
if err == nil {
|
||||||
|
outcome = "success"
|
||||||
|
} else {
|
||||||
|
outcome = "failure"
|
||||||
|
}
|
||||||
|
endTime := time.Now()
|
||||||
|
calculateStateDurations.WithLabelValues(c.algorithm, outcome).Observe(
|
||||||
|
float64(endTime.Sub(c.startTime).Nanoseconds()) / 1000.,
|
||||||
|
)
|
||||||
|
calculateStatePrevEventLength.WithLabelValues(c.algorithm, outcome).Observe(
|
||||||
|
float64(c.prevEventLength),
|
||||||
|
)
|
||||||
|
calculateStateFullStateLength.WithLabelValues(c.algorithm, outcome).Observe(
|
||||||
|
float64(c.fullStateLength),
|
||||||
|
)
|
||||||
|
calculateStateConflictLength.WithLabelValues(c.algorithm, outcome).Observe(
|
||||||
|
float64(c.conflictLength),
|
||||||
|
)
|
||||||
|
return stateNID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
prometheus.MustRegister(
|
||||||
|
calculateStateDurations, calculateStatePrevEventLength,
|
||||||
|
calculateStateFullStateLength, calculateStateConflictLength,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalculateAndStoreStateBeforeEvent calculates a snapshot of the state of a room before an event.
|
||||||
|
// Stores the snapshot of the state in the database.
|
||||||
|
// Returns a numeric ID for the snapshot of the state before the event.
|
||||||
|
func (v StateResolutionV1) CalculateAndStoreStateBeforeEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
event gomatrixserverlib.Event,
|
||||||
|
roomNID types.RoomNID,
|
||||||
|
) (types.StateSnapshotNID, error) {
|
||||||
|
// Load the state at the prev events.
|
||||||
|
prevEventRefs := event.PrevEvents()
|
||||||
|
prevEventIDs := make([]string, len(prevEventRefs))
|
||||||
|
for i := range prevEventRefs {
|
||||||
|
prevEventIDs[i] = prevEventRefs[i].EventID
|
||||||
|
}
|
||||||
|
|
||||||
|
prevStates, err := v.db.StateAtEventIDs(ctx, prevEventIDs)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The state before this event will be the state after the events that came before it.
|
||||||
|
return v.CalculateAndStoreStateAfterEvents(ctx, roomNID, prevStates)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalculateAndStoreStateAfterEvents finds the room state after the given events.
|
||||||
|
// Stores the resulting state in the database and returns a numeric ID for that snapshot.
|
||||||
|
func (v StateResolutionV1) CalculateAndStoreStateAfterEvents(
|
||||||
|
ctx context.Context,
|
||||||
|
roomNID types.RoomNID,
|
||||||
|
prevStates []types.StateAtEvent,
|
||||||
|
) (types.StateSnapshotNID, error) {
|
||||||
|
metrics := calculateStateMetrics{startTime: time.Now(), prevEventLength: len(prevStates)}
|
||||||
|
|
||||||
|
if len(prevStates) == 0 {
|
||||||
|
// 2) There weren't any prev_events for this event so the state is
|
||||||
|
// empty.
|
||||||
|
metrics.algorithm = "empty_state"
|
||||||
|
return metrics.stop(v.db.AddState(ctx, roomNID, nil, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(prevStates) == 1 {
|
||||||
|
prevState := prevStates[0]
|
||||||
|
if prevState.EventStateKeyNID == 0 {
|
||||||
|
// 3) None of the previous events were state events and they all
|
||||||
|
// have the same state, so this event has exactly the same state
|
||||||
|
// as the previous events.
|
||||||
|
// This should be the common case.
|
||||||
|
metrics.algorithm = "no_change"
|
||||||
|
return metrics.stop(prevState.BeforeStateSnapshotNID, nil)
|
||||||
|
}
|
||||||
|
// The previous event was a state event so we need to store a copy
|
||||||
|
// of the previous state updated with that event.
|
||||||
|
stateBlockNIDLists, err := v.db.StateBlockNIDs(
|
||||||
|
ctx, []types.StateSnapshotNID{prevState.BeforeStateSnapshotNID},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
metrics.algorithm = "_load_state_blocks"
|
||||||
|
return metrics.stop(0, err)
|
||||||
|
}
|
||||||
|
stateBlockNIDs := stateBlockNIDLists[0].StateBlockNIDs
|
||||||
|
if len(stateBlockNIDs) < maxStateBlockNIDs {
|
||||||
|
// 4) The number of state data blocks is small enough that we can just
|
||||||
|
// add the state event as a block of size one to the end of the blocks.
|
||||||
|
metrics.algorithm = "single_delta"
|
||||||
|
return metrics.stop(v.db.AddState(
|
||||||
|
ctx, roomNID, stateBlockNIDs, []types.StateEntry{prevState.StateEntry},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
// If there are too many deltas then we need to calculate the full state
|
||||||
|
// So fall through to calculateAndStoreStateAfterManyEvents
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.calculateAndStoreStateAfterManyEvents(ctx, roomNID, prevStates, metrics)
|
||||||
|
}
|
||||||
|
|
||||||
|
// maxStateBlockNIDs is the maximum number of state data blocks to use to encode a snapshot of room state.
|
||||||
|
// Increasing this number means that we can encode more of the state changes as simple deltas which means that
|
||||||
|
// we need fewer entries in the state data table. However making this number bigger will increase the size of
|
||||||
|
// the rows in the state table itself and will require more index lookups when retrieving a snapshot.
|
||||||
|
// TODO: Tune this to get the right balance between size and lookup performance.
|
||||||
|
const maxStateBlockNIDs = 64
|
||||||
|
|
||||||
|
// calculateAndStoreStateAfterManyEvents finds the room state after the given events.
|
||||||
|
// This handles the slow path of calculateAndStoreStateAfterEvents for when there is more than one event.
|
||||||
|
// Stores the resulting state and returns a numeric ID for the snapshot.
|
||||||
|
func (v StateResolutionV1) calculateAndStoreStateAfterManyEvents(
|
||||||
|
ctx context.Context,
|
||||||
|
roomNID types.RoomNID,
|
||||||
|
prevStates []types.StateAtEvent,
|
||||||
|
metrics calculateStateMetrics,
|
||||||
|
) (types.StateSnapshotNID, error) {
|
||||||
|
|
||||||
|
state, algorithm, conflictLength, err :=
|
||||||
|
v.calculateStateAfterManyEvents(ctx, prevStates)
|
||||||
|
metrics.algorithm = algorithm
|
||||||
|
if err != nil {
|
||||||
|
return metrics.stop(0, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Check if we can encode the new state as a delta against the
|
||||||
|
// previous state.
|
||||||
|
metrics.conflictLength = conflictLength
|
||||||
|
metrics.fullStateLength = len(state)
|
||||||
|
return metrics.stop(v.db.AddState(ctx, roomNID, nil, state))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v StateResolutionV1) calculateStateAfterManyEvents(
|
||||||
|
ctx context.Context, prevStates []types.StateAtEvent,
|
||||||
|
) (state []types.StateEntry, algorithm string, conflictLength int, err error) {
|
||||||
|
var combined []types.StateEntry
|
||||||
|
// Conflict resolution.
|
||||||
|
// First stage: load the state after each of the prev events.
|
||||||
|
combined, err = v.LoadCombinedStateAfterEvents(ctx, prevStates)
|
||||||
|
if err != nil {
|
||||||
|
algorithm = "_load_combined_state"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all the entries with the same type and key together.
|
||||||
|
// We don't care about the order here because the conflict resolution
|
||||||
|
// algorithm doesn't depend on the order of the prev events.
|
||||||
|
// Remove duplicate entires.
|
||||||
|
combined = combined[:util.SortAndUnique(stateEntrySorter(combined))]
|
||||||
|
|
||||||
|
// Find the conflicts
|
||||||
|
conflicts := findDuplicateStateKeys(combined)
|
||||||
|
|
||||||
|
if len(conflicts) > 0 {
|
||||||
|
conflictLength = len(conflicts)
|
||||||
|
|
||||||
|
// 5) There are conflicting state events, for each conflict workout
|
||||||
|
// what the appropriate state event is.
|
||||||
|
|
||||||
|
// Work out which entries aren't conflicted.
|
||||||
|
var notConflicted []types.StateEntry
|
||||||
|
for _, entry := range combined {
|
||||||
|
if _, ok := stateEntryMap(conflicts).lookup(entry.StateKeyTuple); !ok {
|
||||||
|
notConflicted = append(notConflicted, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolved []types.StateEntry
|
||||||
|
resolved, err = v.resolveConflicts(ctx, notConflicted, conflicts)
|
||||||
|
if err != nil {
|
||||||
|
algorithm = "_resolve_conflicts"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
algorithm = "full_state_with_conflicts"
|
||||||
|
state = resolved
|
||||||
|
} else {
|
||||||
|
algorithm = "full_state_no_conflicts"
|
||||||
|
// 6) There weren't any conflicts
|
||||||
|
state = combined
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveConflicts resolves a list of conflicted state entries. It takes two lists.
|
||||||
|
// The first is a list of all state entries that are not conflicted.
|
||||||
|
// The second is a list of all state entries that are conflicted
|
||||||
|
// A state entry is conflicted when there is more than one numeric event ID for the same state key tuple.
|
||||||
|
// Returns a list that combines the entries without conflicts with the result of state resolution for the entries with conflicts.
|
||||||
|
// The returned list is sorted by state key tuple.
|
||||||
|
// Returns an error if there was a problem talking to the database.
|
||||||
|
func (v StateResolutionV1) resolveConflicts(
|
||||||
|
ctx context.Context,
|
||||||
|
notConflicted, conflicted []types.StateEntry,
|
||||||
|
) ([]types.StateEntry, error) {
|
||||||
|
|
||||||
|
// Load the conflicted events
|
||||||
|
conflictedEvents, eventIDMap, err := v.loadStateEvents(ctx, conflicted)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Work out which auth events we need to load.
|
||||||
|
needed := gomatrixserverlib.StateNeededForAuth(conflictedEvents)
|
||||||
|
|
||||||
|
// Find the numeric IDs for the necessary state keys.
|
||||||
|
var neededStateKeys []string
|
||||||
|
neededStateKeys = append(neededStateKeys, needed.Member...)
|
||||||
|
neededStateKeys = append(neededStateKeys, needed.ThirdPartyInvite...)
|
||||||
|
stateKeyNIDMap, err := v.db.EventStateKeyNIDs(ctx, neededStateKeys)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the necessary auth events.
|
||||||
|
tuplesNeeded := v.stateKeyTuplesNeeded(stateKeyNIDMap, needed)
|
||||||
|
var authEntries []types.StateEntry
|
||||||
|
for _, tuple := range tuplesNeeded {
|
||||||
|
if eventNID, ok := stateEntryMap(notConflicted).lookup(tuple); ok {
|
||||||
|
authEntries = append(authEntries, types.StateEntry{
|
||||||
|
StateKeyTuple: tuple,
|
||||||
|
EventNID: eventNID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authEvents, _, err := v.loadStateEvents(ctx, authEntries)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the conflicts.
|
||||||
|
resolvedEvents := gomatrixserverlib.ResolveStateConflicts(conflictedEvents, authEvents)
|
||||||
|
|
||||||
|
// Map from the full events back to numeric state entries.
|
||||||
|
for _, resolvedEvent := range resolvedEvents {
|
||||||
|
entry, ok := eventIDMap[resolvedEvent.EventID()]
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Errorf("Missing state entry for event ID %q", resolvedEvent.EventID()))
|
||||||
|
}
|
||||||
|
notConflicted = append(notConflicted, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the result so it can be searched.
|
||||||
|
sort.Sort(stateEntrySorter(notConflicted))
|
||||||
|
return notConflicted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateKeyTuplesNeeded works out which numeric state key tuples we need to authenticate some events.
|
||||||
|
func (v StateResolutionV1) stateKeyTuplesNeeded(stateKeyNIDMap map[string]types.EventStateKeyNID, stateNeeded gomatrixserverlib.StateNeeded) []types.StateKeyTuple {
|
||||||
|
var keyTuples []types.StateKeyTuple
|
||||||
|
if stateNeeded.Create {
|
||||||
|
keyTuples = append(keyTuples, types.StateKeyTuple{
|
||||||
|
EventTypeNID: types.MRoomCreateNID,
|
||||||
|
EventStateKeyNID: types.EmptyStateKeyNID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if stateNeeded.PowerLevels {
|
||||||
|
keyTuples = append(keyTuples, types.StateKeyTuple{
|
||||||
|
EventTypeNID: types.MRoomPowerLevelsNID,
|
||||||
|
EventStateKeyNID: types.EmptyStateKeyNID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if stateNeeded.JoinRules {
|
||||||
|
keyTuples = append(keyTuples, types.StateKeyTuple{
|
||||||
|
EventTypeNID: types.MRoomJoinRulesNID,
|
||||||
|
EventStateKeyNID: types.EmptyStateKeyNID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, member := range stateNeeded.Member {
|
||||||
|
stateKeyNID, ok := stateKeyNIDMap[member]
|
||||||
|
if ok {
|
||||||
|
keyTuples = append(keyTuples, types.StateKeyTuple{
|
||||||
|
EventTypeNID: types.MRoomMemberNID,
|
||||||
|
EventStateKeyNID: stateKeyNID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, token := range stateNeeded.ThirdPartyInvite {
|
||||||
|
stateKeyNID, ok := stateKeyNIDMap[token]
|
||||||
|
if ok {
|
||||||
|
keyTuples = append(keyTuples, types.StateKeyTuple{
|
||||||
|
EventTypeNID: types.MRoomThirdPartyInviteNID,
|
||||||
|
EventStateKeyNID: stateKeyNID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keyTuples
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadStateEvents loads the matrix events for a list of state entries.
|
||||||
|
// Returns a list of state events in no particular order and a map from string event ID back to state entry.
|
||||||
|
// The map can be used to recover which numeric state entry a given event is for.
|
||||||
|
// Returns an error if there was a problem talking to the database.
|
||||||
|
func (v StateResolutionV1) loadStateEvents(
|
||||||
|
ctx context.Context, entries []types.StateEntry,
|
||||||
|
) ([]gomatrixserverlib.Event, map[string]types.StateEntry, error) {
|
||||||
|
eventNIDs := make([]types.EventNID, len(entries))
|
||||||
|
for i := range entries {
|
||||||
|
eventNIDs[i] = entries[i].EventNID
|
||||||
|
}
|
||||||
|
events, err := v.db.Events(ctx, eventNIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
eventIDMap := map[string]types.StateEntry{}
|
||||||
|
result := make([]gomatrixserverlib.Event, len(entries))
|
||||||
|
for i := range entries {
|
||||||
|
event, ok := eventMap(events).lookup(entries[i].EventNID)
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Errorf("Corrupt DB: Missing event numeric ID %d", entries[i].EventNID))
|
||||||
|
}
|
||||||
|
result[i] = event.Event
|
||||||
|
eventIDMap[event.Event.EventID()] = entries[i]
|
||||||
|
}
|
||||||
|
return result, eventIDMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findDuplicateStateKeys finds the state entries where the state key tuple appears more than once in a sorted list.
|
||||||
|
// Returns a sorted list of those state entries.
|
||||||
|
func findDuplicateStateKeys(a []types.StateEntry) []types.StateEntry {
|
||||||
|
var result []types.StateEntry
|
||||||
|
// j is the starting index of a block of entries with the same state key tuple.
|
||||||
|
j := 0
|
||||||
|
for i := 1; i < len(a); i++ {
|
||||||
|
// Check if the state key tuple matches the start of the block
|
||||||
|
if a[j].StateKeyTuple != a[i].StateKeyTuple {
|
||||||
|
// If the state key tuple is different then we've reached the end of a block of duplicates.
|
||||||
|
// Check if the size of the block is bigger than one.
|
||||||
|
// If the size is one then there was only a single entry with that state key tuple so we don't add it to the result
|
||||||
|
if j+1 != i {
|
||||||
|
// Add the block to the result.
|
||||||
|
result = append(result, a[j:i]...)
|
||||||
|
}
|
||||||
|
// Start a new block for the next state key tuple.
|
||||||
|
j = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check if the last block with the same state key tuple had more than one event in it.
|
||||||
|
if j+1 != len(a) {
|
||||||
|
result = append(result, a[j:]...)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
type stateEntrySorter []types.StateEntry
|
||||||
|
|
||||||
|
func (s stateEntrySorter) Len() int { return len(s) }
|
||||||
|
func (s stateEntrySorter) Less(i, j int) bool { return s[i].LessThan(s[j]) }
|
||||||
|
func (s stateEntrySorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
|
||||||
|
type stateBlockNIDListMap []types.StateBlockNIDList
|
||||||
|
|
||||||
|
func (m stateBlockNIDListMap) lookup(stateNID types.StateSnapshotNID) (stateBlockNIDs []types.StateBlockNID, ok bool) {
|
||||||
|
list := []types.StateBlockNIDList(m)
|
||||||
|
i := sort.Search(len(list), func(i int) bool {
|
||||||
|
return list[i].StateSnapshotNID >= stateNID
|
||||||
|
})
|
||||||
|
if i < len(list) && list[i].StateSnapshotNID == stateNID {
|
||||||
|
ok = true
|
||||||
|
stateBlockNIDs = list[i].StateBlockNIDs
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type stateEntryListMap []types.StateEntryList
|
||||||
|
|
||||||
|
func (m stateEntryListMap) lookup(stateBlockNID types.StateBlockNID) (stateEntries []types.StateEntry, ok bool) {
|
||||||
|
list := []types.StateEntryList(m)
|
||||||
|
i := sort.Search(len(list), func(i int) bool {
|
||||||
|
return list[i].StateBlockNID >= stateBlockNID
|
||||||
|
})
|
||||||
|
if i < len(list) && list[i].StateBlockNID == stateBlockNID {
|
||||||
|
ok = true
|
||||||
|
stateEntries = list[i].StateEntries
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type stateEntryByStateKeySorter []types.StateEntry
|
||||||
|
|
||||||
|
func (s stateEntryByStateKeySorter) Len() int { return len(s) }
|
||||||
|
func (s stateEntryByStateKeySorter) Less(i, j int) bool {
|
||||||
|
return s[i].StateKeyTuple.LessThan(s[j].StateKeyTuple)
|
||||||
|
}
|
||||||
|
func (s stateEntryByStateKeySorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
|
||||||
|
type stateNIDSorter []types.StateSnapshotNID
|
||||||
|
|
||||||
|
func (s stateNIDSorter) Len() int { return len(s) }
|
||||||
|
func (s stateNIDSorter) Less(i, j int) bool { return s[i] < s[j] }
|
||||||
|
func (s stateNIDSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
|
||||||
|
func uniqueStateSnapshotNIDs(nids []types.StateSnapshotNID) []types.StateSnapshotNID {
|
||||||
|
return nids[:util.SortAndUnique(stateNIDSorter(nids))]
|
||||||
|
}
|
||||||
|
|
||||||
|
type stateBlockNIDSorter []types.StateBlockNID
|
||||||
|
|
||||||
|
func (s stateBlockNIDSorter) Len() int { return len(s) }
|
||||||
|
func (s stateBlockNIDSorter) Less(i, j int) bool { return s[i] < s[j] }
|
||||||
|
func (s stateBlockNIDSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
|
||||||
|
func uniqueStateBlockNIDs(nids []types.StateBlockNID) []types.StateBlockNID {
|
||||||
|
return nids[:util.SortAndUnique(stateBlockNIDSorter(nids))]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map from event type, state key tuple to numeric event ID.
|
||||||
|
// Implemented using binary search on a sorted array.
|
||||||
|
type stateEntryMap []types.StateEntry
|
||||||
|
|
||||||
|
// lookup an entry in the event map.
|
||||||
|
func (m stateEntryMap) lookup(stateKey types.StateKeyTuple) (eventNID types.EventNID, ok bool) {
|
||||||
|
// Since the list is sorted we can implement this using binary search.
|
||||||
|
// This is faster than using a hash map.
|
||||||
|
// We don't have to worry about pathological cases because the keys are fixed
|
||||||
|
// size and are controlled by us.
|
||||||
|
list := []types.StateEntry(m)
|
||||||
|
i := sort.Search(len(list), func(i int) bool {
|
||||||
|
return !list[i].StateKeyTuple.LessThan(stateKey)
|
||||||
|
})
|
||||||
|
if i < len(list) && list[i].StateKeyTuple == stateKey {
|
||||||
|
ok = true
|
||||||
|
eventNID = list[i].EventNID
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map from numeric event ID to event.
|
||||||
|
// Implemented using binary search on a sorted array.
|
||||||
|
type eventMap []types.Event
|
||||||
|
|
||||||
|
// lookup an entry in the event map.
|
||||||
|
func (m eventMap) lookup(eventNID types.EventNID) (event *types.Event, ok bool) {
|
||||||
|
// Since the list is sorted we can implement this using binary search.
|
||||||
|
// This is faster than using a hash map.
|
||||||
|
// We don't have to worry about pathological cases because the keys are fixed
|
||||||
|
// size are controlled by us.
|
||||||
|
list := []types.Event(m)
|
||||||
|
i := sort.Search(len(list), func(i int) bool {
|
||||||
|
return list[i].EventNID >= eventNID
|
||||||
|
})
|
||||||
|
if i < len(list) && list[i].EventNID == eventNID {
|
||||||
|
ok = true
|
||||||
|
event = &list[i]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
// Copyright 2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +14,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package state
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -38,7 +39,10 @@ CREATE TABLE IF NOT EXISTS roomserver_rooms (
|
||||||
last_event_sent_nid BIGINT NOT NULL DEFAULT 0,
|
last_event_sent_nid BIGINT NOT NULL DEFAULT 0,
|
||||||
-- The state of the room after the current set of latest events.
|
-- The state of the room after the current set of latest events.
|
||||||
-- This will be 0 if there are no latest events in the room.
|
-- This will be 0 if there are no latest events in the room.
|
||||||
state_snapshot_nid BIGINT NOT NULL DEFAULT 0
|
state_snapshot_nid BIGINT NOT NULL DEFAULT 0,
|
||||||
|
-- The version of the room, which will assist in determining the state resolution
|
||||||
|
-- algorithm, event ID format, etc.
|
||||||
|
room_version BIGINT NOT NULL DEFAULT 1
|
||||||
);
|
);
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
@ -60,12 +64,16 @@ const selectLatestEventNIDsForUpdateSQL = "" +
|
||||||
const updateLatestEventNIDsSQL = "" +
|
const updateLatestEventNIDsSQL = "" +
|
||||||
"UPDATE roomserver_rooms SET latest_event_nids = $2, last_event_sent_nid = $3, state_snapshot_nid = $4 WHERE room_nid = $1"
|
"UPDATE roomserver_rooms SET latest_event_nids = $2, last_event_sent_nid = $3, state_snapshot_nid = $4 WHERE room_nid = $1"
|
||||||
|
|
||||||
|
const selectRoomVersionForRoomNIDSQL = "" +
|
||||||
|
"SELECT room_version FROM roomserver_rooms WHERE room_nid = $1"
|
||||||
|
|
||||||
type roomStatements struct {
|
type roomStatements struct {
|
||||||
insertRoomNIDStmt *sql.Stmt
|
insertRoomNIDStmt *sql.Stmt
|
||||||
selectRoomNIDStmt *sql.Stmt
|
selectRoomNIDStmt *sql.Stmt
|
||||||
selectLatestEventNIDsStmt *sql.Stmt
|
selectLatestEventNIDsStmt *sql.Stmt
|
||||||
selectLatestEventNIDsForUpdateStmt *sql.Stmt
|
selectLatestEventNIDsForUpdateStmt *sql.Stmt
|
||||||
updateLatestEventNIDsStmt *sql.Stmt
|
updateLatestEventNIDsStmt *sql.Stmt
|
||||||
|
selectRoomVersionForRoomNIDStmt *sql.Stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *roomStatements) prepare(db *sql.DB) (err error) {
|
func (s *roomStatements) prepare(db *sql.DB) (err error) {
|
||||||
|
|
@ -79,6 +87,7 @@ func (s *roomStatements) prepare(db *sql.DB) (err error) {
|
||||||
{&s.selectLatestEventNIDsStmt, selectLatestEventNIDsSQL},
|
{&s.selectLatestEventNIDsStmt, selectLatestEventNIDsSQL},
|
||||||
{&s.selectLatestEventNIDsForUpdateStmt, selectLatestEventNIDsForUpdateSQL},
|
{&s.selectLatestEventNIDsForUpdateStmt, selectLatestEventNIDsForUpdateSQL},
|
||||||
{&s.updateLatestEventNIDsStmt, updateLatestEventNIDsSQL},
|
{&s.updateLatestEventNIDsStmt, updateLatestEventNIDsSQL},
|
||||||
|
{&s.selectRoomVersionForRoomNIDStmt, selectRoomVersionForRoomNIDSQL},
|
||||||
}.prepare(db)
|
}.prepare(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,3 +162,12 @@ func (s *roomStatements) updateLatestEventNIDs(
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *roomStatements) selectRoomVersionForRoomNID(
|
||||||
|
ctx context.Context, txn *sql.Tx, roomNID types.RoomNID,
|
||||||
|
) (int64, error) {
|
||||||
|
var roomVersion int64
|
||||||
|
stmt := common.TxStmt(txn, s.selectRoomVersionForRoomNIDStmt)
|
||||||
|
err := stmt.QueryRowContext(ctx, roomNID).Scan(&roomVersion)
|
||||||
|
return roomVersion, err
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
721
roomserver/storage/postgres/storage.go
Normal file
721
roomserver/storage/postgres/storage.go
Normal file
|
|
@ -0,0 +1,721 @@
|
||||||
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
// Import the postgres database driver.
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Database is used to store room events and stream offsets.
|
||||||
|
type Database struct {
|
||||||
|
statements statements
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open a postgres database.
|
||||||
|
func Open(dataSourceName string) (*Database, error) {
|
||||||
|
var d Database
|
||||||
|
var err error
|
||||||
|
if d.db, err = sql.Open("postgres", dataSourceName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = d.statements.prepare(d.db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreEvent implements input.EventDatabase
|
||||||
|
func (d *Database) StoreEvent(
|
||||||
|
ctx context.Context, event gomatrixserverlib.Event,
|
||||||
|
txnAndSessionID *api.TransactionID, authEventNIDs []types.EventNID,
|
||||||
|
) (types.RoomNID, types.StateAtEvent, error) {
|
||||||
|
var (
|
||||||
|
roomNID types.RoomNID
|
||||||
|
eventTypeNID types.EventTypeNID
|
||||||
|
eventStateKeyNID types.EventStateKeyNID
|
||||||
|
eventNID types.EventNID
|
||||||
|
stateNID types.StateSnapshotNID
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if txnAndSessionID != nil {
|
||||||
|
if err = d.statements.insertTransaction(
|
||||||
|
ctx, txnAndSessionID.TransactionID,
|
||||||
|
txnAndSessionID.SessionID, event.Sender(), event.EventID(),
|
||||||
|
); err != nil {
|
||||||
|
return 0, types.StateAtEvent{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if roomNID, err = d.assignRoomNID(ctx, nil, event.RoomID()); err != nil {
|
||||||
|
return 0, types.StateAtEvent{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if eventTypeNID, err = d.assignEventTypeNID(ctx, event.Type()); err != nil {
|
||||||
|
return 0, types.StateAtEvent{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
eventStateKey := event.StateKey()
|
||||||
|
// Assigned a numeric ID for the state_key if there is one present.
|
||||||
|
// Otherwise set the numeric ID for the state_key to 0.
|
||||||
|
if eventStateKey != nil {
|
||||||
|
if eventStateKeyNID, err = d.assignStateKeyNID(ctx, nil, *eventStateKey); err != nil {
|
||||||
|
return 0, types.StateAtEvent{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if eventNID, stateNID, err = d.statements.insertEvent(
|
||||||
|
ctx,
|
||||||
|
roomNID,
|
||||||
|
eventTypeNID,
|
||||||
|
eventStateKeyNID,
|
||||||
|
event.EventID(),
|
||||||
|
event.EventReference().EventSHA256,
|
||||||
|
authEventNIDs,
|
||||||
|
event.Depth(),
|
||||||
|
); err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
// We've already inserted the event so select the numeric event ID
|
||||||
|
eventNID, stateNID, err = d.statements.selectEvent(ctx, event.EventID())
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0, types.StateAtEvent{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = d.statements.insertEventJSON(ctx, eventNID, event.JSON()); err != nil {
|
||||||
|
return 0, types.StateAtEvent{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return roomNID, types.StateAtEvent{
|
||||||
|
BeforeStateSnapshotNID: stateNID,
|
||||||
|
StateEntry: types.StateEntry{
|
||||||
|
StateKeyTuple: types.StateKeyTuple{
|
||||||
|
EventTypeNID: eventTypeNID,
|
||||||
|
EventStateKeyNID: eventStateKeyNID,
|
||||||
|
},
|
||||||
|
EventNID: eventNID,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) assignRoomNID(
|
||||||
|
ctx context.Context, txn *sql.Tx, roomID string,
|
||||||
|
) (types.RoomNID, error) {
|
||||||
|
// Check if we already have a numeric ID in the database.
|
||||||
|
roomNID, err := d.statements.selectRoomNID(ctx, txn, roomID)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
// We don't have a numeric ID so insert one into the database.
|
||||||
|
roomNID, err = d.statements.insertRoomNID(ctx, txn, roomID)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
// We raced with another insert so run the select again.
|
||||||
|
roomNID, err = d.statements.selectRoomNID(ctx, txn, roomID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roomNID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) assignEventTypeNID(
|
||||||
|
ctx context.Context, eventType string,
|
||||||
|
) (types.EventTypeNID, error) {
|
||||||
|
// Check if we already have a numeric ID in the database.
|
||||||
|
eventTypeNID, err := d.statements.selectEventTypeNID(ctx, eventType)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
// We don't have a numeric ID so insert one into the database.
|
||||||
|
eventTypeNID, err = d.statements.insertEventTypeNID(ctx, eventType)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
// We raced with another insert so run the select again.
|
||||||
|
eventTypeNID, err = d.statements.selectEventTypeNID(ctx, eventType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return eventTypeNID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) assignStateKeyNID(
|
||||||
|
ctx context.Context, txn *sql.Tx, eventStateKey string,
|
||||||
|
) (types.EventStateKeyNID, error) {
|
||||||
|
// Check if we already have a numeric ID in the database.
|
||||||
|
eventStateKeyNID, err := d.statements.selectEventStateKeyNID(ctx, txn, eventStateKey)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
// We don't have a numeric ID so insert one into the database.
|
||||||
|
eventStateKeyNID, err = d.statements.insertEventStateKeyNID(ctx, txn, eventStateKey)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
// We raced with another insert so run the select again.
|
||||||
|
eventStateKeyNID, err = d.statements.selectEventStateKeyNID(ctx, txn, eventStateKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return eventStateKeyNID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateEntriesForEventIDs implements input.EventDatabase
|
||||||
|
func (d *Database) StateEntriesForEventIDs(
|
||||||
|
ctx context.Context, eventIDs []string,
|
||||||
|
) ([]types.StateEntry, error) {
|
||||||
|
return d.statements.bulkSelectStateEventByID(ctx, eventIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventTypeNIDs implements state.RoomStateDatabase
|
||||||
|
func (d *Database) EventTypeNIDs(
|
||||||
|
ctx context.Context, eventTypes []string,
|
||||||
|
) (map[string]types.EventTypeNID, error) {
|
||||||
|
return d.statements.bulkSelectEventTypeNID(ctx, eventTypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventStateKeyNIDs implements state.RoomStateDatabase
|
||||||
|
func (d *Database) EventStateKeyNIDs(
|
||||||
|
ctx context.Context, eventStateKeys []string,
|
||||||
|
) (map[string]types.EventStateKeyNID, error) {
|
||||||
|
return d.statements.bulkSelectEventStateKeyNID(ctx, eventStateKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventStateKeys implements query.RoomserverQueryAPIDatabase
|
||||||
|
func (d *Database) EventStateKeys(
|
||||||
|
ctx context.Context, eventStateKeyNIDs []types.EventStateKeyNID,
|
||||||
|
) (map[types.EventStateKeyNID]string, error) {
|
||||||
|
return d.statements.bulkSelectEventStateKey(ctx, eventStateKeyNIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventNIDs implements query.RoomserverQueryAPIDatabase
|
||||||
|
func (d *Database) EventNIDs(
|
||||||
|
ctx context.Context, eventIDs []string,
|
||||||
|
) (map[string]types.EventNID, error) {
|
||||||
|
return d.statements.bulkSelectEventNID(ctx, eventIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Events implements input.EventDatabase
|
||||||
|
func (d *Database) Events(
|
||||||
|
ctx context.Context, eventNIDs []types.EventNID,
|
||||||
|
) ([]types.Event, error) {
|
||||||
|
eventJSONs, err := d.statements.bulkSelectEventJSON(ctx, eventNIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
results := make([]types.Event, len(eventJSONs))
|
||||||
|
for i, eventJSON := range eventJSONs {
|
||||||
|
result := &results[i]
|
||||||
|
result.EventNID = eventJSON.EventNID
|
||||||
|
// TODO: Use NewEventFromTrustedJSON for efficiency
|
||||||
|
result.Event, err = gomatrixserverlib.NewEventFromUntrustedJSON(eventJSON.EventJSON)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddState implements input.EventDatabase
|
||||||
|
func (d *Database) AddState(
|
||||||
|
ctx context.Context,
|
||||||
|
roomNID types.RoomNID,
|
||||||
|
stateBlockNIDs []types.StateBlockNID,
|
||||||
|
state []types.StateEntry,
|
||||||
|
) (types.StateSnapshotNID, error) {
|
||||||
|
if len(state) > 0 {
|
||||||
|
stateBlockNID, err := d.statements.selectNextStateBlockNID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if err = d.statements.bulkInsertStateData(ctx, stateBlockNID, state); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
stateBlockNIDs = append(stateBlockNIDs[:len(stateBlockNIDs):len(stateBlockNIDs)], stateBlockNID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.statements.insertState(ctx, roomNID, stateBlockNIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetState implements input.EventDatabase
|
||||||
|
func (d *Database) SetState(
|
||||||
|
ctx context.Context, eventNID types.EventNID, stateNID types.StateSnapshotNID,
|
||||||
|
) error {
|
||||||
|
return d.statements.updateEventState(ctx, eventNID, stateNID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateAtEventIDs implements input.EventDatabase
|
||||||
|
func (d *Database) StateAtEventIDs(
|
||||||
|
ctx context.Context, eventIDs []string,
|
||||||
|
) ([]types.StateAtEvent, error) {
|
||||||
|
return d.statements.bulkSelectStateAtEventByID(ctx, eventIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateBlockNIDs implements state.RoomStateDatabase
|
||||||
|
func (d *Database) StateBlockNIDs(
|
||||||
|
ctx context.Context, stateNIDs []types.StateSnapshotNID,
|
||||||
|
) ([]types.StateBlockNIDList, error) {
|
||||||
|
return d.statements.bulkSelectStateBlockNIDs(ctx, stateNIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateEntries implements state.RoomStateDatabase
|
||||||
|
func (d *Database) StateEntries(
|
||||||
|
ctx context.Context, stateBlockNIDs []types.StateBlockNID,
|
||||||
|
) ([]types.StateEntryList, error) {
|
||||||
|
return d.statements.bulkSelectStateBlockEntries(ctx, stateBlockNIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SnapshotNIDFromEventID implements state.RoomStateDatabase
|
||||||
|
func (d *Database) SnapshotNIDFromEventID(
|
||||||
|
ctx context.Context, eventID string,
|
||||||
|
) (types.StateSnapshotNID, error) {
|
||||||
|
_, stateNID, err := d.statements.selectEvent(ctx, eventID)
|
||||||
|
return stateNID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventIDs implements input.RoomEventDatabase
|
||||||
|
func (d *Database) EventIDs(
|
||||||
|
ctx context.Context, eventNIDs []types.EventNID,
|
||||||
|
) (map[types.EventNID]string, error) {
|
||||||
|
return d.statements.bulkSelectEventID(ctx, eventNIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLatestEventsForUpdate implements input.EventDatabase
|
||||||
|
func (d *Database) GetLatestEventsForUpdate(
|
||||||
|
ctx context.Context, roomNID types.RoomNID,
|
||||||
|
) (types.RoomRecentEventsUpdater, error) {
|
||||||
|
txn, err := d.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
eventNIDs, lastEventNIDSent, currentStateSnapshotNID, err :=
|
||||||
|
d.statements.selectLatestEventsNIDsForUpdate(ctx, txn, roomNID)
|
||||||
|
if err != nil {
|
||||||
|
txn.Rollback() // nolint: errcheck
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stateAndRefs, err := d.statements.bulkSelectStateAtEventAndReference(ctx, txn, eventNIDs)
|
||||||
|
if err != nil {
|
||||||
|
txn.Rollback() // nolint: errcheck
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var lastEventIDSent string
|
||||||
|
if lastEventNIDSent != 0 {
|
||||||
|
lastEventIDSent, err = d.statements.selectEventID(ctx, txn, lastEventNIDSent)
|
||||||
|
if err != nil {
|
||||||
|
txn.Rollback() // nolint: errcheck
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &roomRecentEventsUpdater{
|
||||||
|
transaction{ctx, txn}, d, roomNID, stateAndRefs, lastEventIDSent, currentStateSnapshotNID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionEventID implements input.EventDatabase
|
||||||
|
func (d *Database) GetTransactionEventID(
|
||||||
|
ctx context.Context, transactionID string,
|
||||||
|
sessionID int64, userID string,
|
||||||
|
) (string, error) {
|
||||||
|
eventID, err := d.statements.selectTransactionEventID(ctx, transactionID, sessionID, userID)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return eventID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type roomRecentEventsUpdater struct {
|
||||||
|
transaction
|
||||||
|
d *Database
|
||||||
|
roomNID types.RoomNID
|
||||||
|
latestEvents []types.StateAtEventAndReference
|
||||||
|
lastEventIDSent string
|
||||||
|
currentStateSnapshotNID types.StateSnapshotNID
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatestEvents implements types.RoomRecentEventsUpdater
|
||||||
|
func (u *roomRecentEventsUpdater) LatestEvents() []types.StateAtEventAndReference {
|
||||||
|
return u.latestEvents
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastEventIDSent implements types.RoomRecentEventsUpdater
|
||||||
|
func (u *roomRecentEventsUpdater) LastEventIDSent() string {
|
||||||
|
return u.lastEventIDSent
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentStateSnapshotNID implements types.RoomRecentEventsUpdater
|
||||||
|
func (u *roomRecentEventsUpdater) CurrentStateSnapshotNID() types.StateSnapshotNID {
|
||||||
|
return u.currentStateSnapshotNID
|
||||||
|
}
|
||||||
|
|
||||||
|
// StorePreviousEvents implements types.RoomRecentEventsUpdater
|
||||||
|
func (u *roomRecentEventsUpdater) StorePreviousEvents(eventNID types.EventNID, previousEventReferences []gomatrixserverlib.EventReference) error {
|
||||||
|
for _, ref := range previousEventReferences {
|
||||||
|
if err := u.d.statements.insertPreviousEvent(u.ctx, u.txn, ref.EventID, ref.EventSHA256, eventNID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsReferenced implements types.RoomRecentEventsUpdater
|
||||||
|
func (u *roomRecentEventsUpdater) IsReferenced(eventReference gomatrixserverlib.EventReference) (bool, error) {
|
||||||
|
err := u.d.statements.selectPreviousEventExists(u.ctx, u.txn, eventReference.EventID, eventReference.EventSHA256)
|
||||||
|
if err == nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLatestEvents implements types.RoomRecentEventsUpdater
|
||||||
|
func (u *roomRecentEventsUpdater) SetLatestEvents(
|
||||||
|
roomNID types.RoomNID, latest []types.StateAtEventAndReference, lastEventNIDSent types.EventNID,
|
||||||
|
currentStateSnapshotNID types.StateSnapshotNID,
|
||||||
|
) error {
|
||||||
|
eventNIDs := make([]types.EventNID, len(latest))
|
||||||
|
for i := range latest {
|
||||||
|
eventNIDs[i] = latest[i].EventNID
|
||||||
|
}
|
||||||
|
return u.d.statements.updateLatestEventNIDs(u.ctx, u.txn, roomNID, eventNIDs, lastEventNIDSent, currentStateSnapshotNID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasEventBeenSent implements types.RoomRecentEventsUpdater
|
||||||
|
func (u *roomRecentEventsUpdater) HasEventBeenSent(eventNID types.EventNID) (bool, error) {
|
||||||
|
return u.d.statements.selectEventSentToOutput(u.ctx, u.txn, eventNID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkEventAsSent implements types.RoomRecentEventsUpdater
|
||||||
|
func (u *roomRecentEventsUpdater) MarkEventAsSent(eventNID types.EventNID) error {
|
||||||
|
return u.d.statements.updateEventSentToOutput(u.ctx, u.txn, eventNID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *roomRecentEventsUpdater) MembershipUpdater(targetUserNID types.EventStateKeyNID) (types.MembershipUpdater, error) {
|
||||||
|
return u.d.membershipUpdaterTxn(u.ctx, u.txn, u.roomNID, targetUserNID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoomNID implements query.RoomserverQueryAPIDB
|
||||||
|
func (d *Database) RoomNID(ctx context.Context, roomID string) (types.RoomNID, error) {
|
||||||
|
roomNID, err := d.statements.selectRoomNID(ctx, nil, roomID)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return roomNID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatestEventIDs implements query.RoomserverQueryAPIDatabase
|
||||||
|
func (d *Database) LatestEventIDs(
|
||||||
|
ctx context.Context, roomNID types.RoomNID,
|
||||||
|
) ([]gomatrixserverlib.EventReference, types.StateSnapshotNID, int64, error) {
|
||||||
|
eventNIDs, currentStateSnapshotNID, err := d.statements.selectLatestEventNIDs(ctx, roomNID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, err
|
||||||
|
}
|
||||||
|
references, err := d.statements.bulkSelectEventReference(ctx, eventNIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, err
|
||||||
|
}
|
||||||
|
depth, err := d.statements.selectMaxEventDepth(ctx, eventNIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, err
|
||||||
|
}
|
||||||
|
return references, currentStateSnapshotNID, depth, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInvitesForUser implements query.RoomserverQueryAPIDatabase
|
||||||
|
func (d *Database) GetInvitesForUser(
|
||||||
|
ctx context.Context,
|
||||||
|
roomNID types.RoomNID,
|
||||||
|
targetUserNID types.EventStateKeyNID,
|
||||||
|
) (senderUserIDs []types.EventStateKeyNID, err error) {
|
||||||
|
return d.statements.selectInviteActiveForUserInRoom(ctx, targetUserNID, roomNID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRoomAlias implements alias.RoomserverAliasAPIDB
|
||||||
|
func (d *Database) SetRoomAlias(ctx context.Context, alias string, roomID string, creatorUserID string) error {
|
||||||
|
return d.statements.insertRoomAlias(ctx, alias, roomID, creatorUserID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoomIDForAlias implements alias.RoomserverAliasAPIDB
|
||||||
|
func (d *Database) GetRoomIDForAlias(ctx context.Context, alias string) (string, error) {
|
||||||
|
return d.statements.selectRoomIDFromAlias(ctx, alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAliasesForRoomID implements alias.RoomserverAliasAPIDB
|
||||||
|
func (d *Database) GetAliasesForRoomID(ctx context.Context, roomID string) ([]string, error) {
|
||||||
|
return d.statements.selectAliasesFromRoomID(ctx, roomID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCreatorIDForAlias implements alias.RoomserverAliasAPIDB
|
||||||
|
func (d *Database) GetCreatorIDForAlias(
|
||||||
|
ctx context.Context, alias string,
|
||||||
|
) (string, error) {
|
||||||
|
return d.statements.selectCreatorIDFromAlias(ctx, alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveRoomAlias implements alias.RoomserverAliasAPIDB
|
||||||
|
func (d *Database) RemoveRoomAlias(ctx context.Context, alias string) error {
|
||||||
|
return d.statements.deleteRoomAlias(ctx, alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateEntriesForTuples implements state.RoomStateDatabase
|
||||||
|
func (d *Database) StateEntriesForTuples(
|
||||||
|
ctx context.Context,
|
||||||
|
stateBlockNIDs []types.StateBlockNID,
|
||||||
|
stateKeyTuples []types.StateKeyTuple,
|
||||||
|
) ([]types.StateEntryList, error) {
|
||||||
|
return d.statements.bulkSelectFilteredStateBlockEntries(
|
||||||
|
ctx, stateBlockNIDs, stateKeyTuples,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MembershipUpdater implements input.RoomEventDatabase
|
||||||
|
func (d *Database) MembershipUpdater(
|
||||||
|
ctx context.Context, roomID, targetUserID string,
|
||||||
|
) (types.MembershipUpdater, error) {
|
||||||
|
txn, err := d.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
succeeded := false
|
||||||
|
defer func() {
|
||||||
|
if !succeeded {
|
||||||
|
txn.Rollback() // nolint: errcheck
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
roomNID, err := d.assignRoomNID(ctx, txn, roomID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetUserNID, err := d.assignStateKeyNID(ctx, txn, targetUserID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updater, err := d.membershipUpdaterTxn(ctx, txn, roomNID, targetUserNID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
succeeded = true
|
||||||
|
return updater, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type membershipUpdater struct {
|
||||||
|
transaction
|
||||||
|
d *Database
|
||||||
|
roomNID types.RoomNID
|
||||||
|
targetUserNID types.EventStateKeyNID
|
||||||
|
membership membershipState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) membershipUpdaterTxn(
|
||||||
|
ctx context.Context,
|
||||||
|
txn *sql.Tx,
|
||||||
|
roomNID types.RoomNID,
|
||||||
|
targetUserNID types.EventStateKeyNID,
|
||||||
|
) (types.MembershipUpdater, error) {
|
||||||
|
|
||||||
|
if err := d.statements.insertMembership(ctx, txn, roomNID, targetUserNID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
membership, err := d.statements.selectMembershipForUpdate(ctx, txn, roomNID, targetUserNID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &membershipUpdater{
|
||||||
|
transaction{ctx, txn}, d, roomNID, targetUserNID, membership,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsInvite implements types.MembershipUpdater
|
||||||
|
func (u *membershipUpdater) IsInvite() bool {
|
||||||
|
return u.membership == membershipStateInvite
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsJoin implements types.MembershipUpdater
|
||||||
|
func (u *membershipUpdater) IsJoin() bool {
|
||||||
|
return u.membership == membershipStateJoin
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLeave implements types.MembershipUpdater
|
||||||
|
func (u *membershipUpdater) IsLeave() bool {
|
||||||
|
return u.membership == membershipStateLeaveOrBan
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetToInvite implements types.MembershipUpdater
|
||||||
|
func (u *membershipUpdater) SetToInvite(event gomatrixserverlib.Event) (bool, error) {
|
||||||
|
senderUserNID, err := u.d.assignStateKeyNID(u.ctx, u.txn, event.Sender())
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
inserted, err := u.d.statements.insertInviteEvent(
|
||||||
|
u.ctx, u.txn, event.EventID(), u.roomNID, u.targetUserNID, senderUserNID, event.JSON(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if u.membership != membershipStateInvite {
|
||||||
|
if err = u.d.statements.updateMembership(
|
||||||
|
u.ctx, u.txn, u.roomNID, u.targetUserNID, senderUserNID, membershipStateInvite, 0,
|
||||||
|
); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inserted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetToJoin implements types.MembershipUpdater
|
||||||
|
func (u *membershipUpdater) SetToJoin(senderUserID string, eventID string, isUpdate bool) ([]string, error) {
|
||||||
|
var inviteEventIDs []string
|
||||||
|
|
||||||
|
senderUserNID, err := u.d.assignStateKeyNID(u.ctx, u.txn, senderUserID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a join event update, there is no invite to update
|
||||||
|
if !isUpdate {
|
||||||
|
inviteEventIDs, err = u.d.statements.updateInviteRetired(
|
||||||
|
u.ctx, u.txn, u.roomNID, u.targetUserNID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up the NID of the new join event
|
||||||
|
nIDs, err := u.d.EventNIDs(u.ctx, []string{eventID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.membership != membershipStateJoin || isUpdate {
|
||||||
|
if err = u.d.statements.updateMembership(
|
||||||
|
u.ctx, u.txn, u.roomNID, u.targetUserNID, senderUserNID,
|
||||||
|
membershipStateJoin, nIDs[eventID],
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inviteEventIDs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetToLeave implements types.MembershipUpdater
|
||||||
|
func (u *membershipUpdater) SetToLeave(senderUserID string, eventID string) ([]string, error) {
|
||||||
|
senderUserNID, err := u.d.assignStateKeyNID(u.ctx, u.txn, senderUserID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inviteEventIDs, err := u.d.statements.updateInviteRetired(
|
||||||
|
u.ctx, u.txn, u.roomNID, u.targetUserNID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up the NID of the new leave event
|
||||||
|
nIDs, err := u.d.EventNIDs(u.ctx, []string{eventID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.membership != membershipStateLeaveOrBan {
|
||||||
|
if err = u.d.statements.updateMembership(
|
||||||
|
u.ctx, u.txn, u.roomNID, u.targetUserNID, senderUserNID,
|
||||||
|
membershipStateLeaveOrBan, nIDs[eventID],
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inviteEventIDs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMembership implements query.RoomserverQueryAPIDB
|
||||||
|
func (d *Database) GetMembership(
|
||||||
|
ctx context.Context, roomNID types.RoomNID, requestSenderUserID string,
|
||||||
|
) (membershipEventNID types.EventNID, stillInRoom bool, err error) {
|
||||||
|
requestSenderUserNID, err := d.assignStateKeyNID(ctx, nil, requestSenderUserID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
senderMembershipEventNID, senderMembership, err :=
|
||||||
|
d.statements.selectMembershipFromRoomAndTarget(
|
||||||
|
ctx, roomNID, requestSenderUserNID,
|
||||||
|
)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
// The user has never been a member of that room
|
||||||
|
return 0, false, nil
|
||||||
|
} else if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return senderMembershipEventNID, senderMembership == membershipStateJoin, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMembershipEventNIDsForRoom implements query.RoomserverQueryAPIDB
|
||||||
|
func (d *Database) GetMembershipEventNIDsForRoom(
|
||||||
|
ctx context.Context, roomNID types.RoomNID, joinOnly bool,
|
||||||
|
) ([]types.EventNID, error) {
|
||||||
|
if joinOnly {
|
||||||
|
return d.statements.selectMembershipsFromRoomAndMembership(
|
||||||
|
ctx, roomNID, membershipStateJoin,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.statements.selectMembershipsFromRoom(ctx, roomNID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventsFromIDs implements query.RoomserverQueryAPIEventDB
|
||||||
|
func (d *Database) EventsFromIDs(ctx context.Context, eventIDs []string) ([]types.Event, error) {
|
||||||
|
nidMap, err := d.EventNIDs(ctx, eventIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var nids []types.EventNID
|
||||||
|
for _, nid := range nidMap {
|
||||||
|
nids = append(nids, nid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.Events(ctx, nids)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) GetRoomVersionForRoom(
|
||||||
|
ctx context.Context, roomNID types.RoomNID,
|
||||||
|
) (int64, error) {
|
||||||
|
return d.statements.selectRoomVersionForRoomNID(
|
||||||
|
ctx, nil, roomNID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type transaction struct {
|
||||||
|
ctx context.Context
|
||||||
|
txn *sql.Tx
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit implements types.Transaction
|
||||||
|
func (t *transaction) Commit() error {
|
||||||
|
return t.txn.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rollback implements types.Transaction
|
||||||
|
func (t *transaction) Rollback() error {
|
||||||
|
return t.txn.Rollback()
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
|
|
@ -10,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -16,697 +16,57 @@ package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"net/url"
|
||||||
|
|
||||||
// Import the postgres database driver.
|
|
||||||
_ "github.com/lib/pq"
|
|
||||||
"github.com/matrix-org/dendrite/roomserver/api"
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/storage/postgres"
|
||||||
"github.com/matrix-org/dendrite/roomserver/types"
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A Database is used to store room events and stream offsets.
|
type Database interface {
|
||||||
type Database struct {
|
StoreEvent(ctx context.Context, event gomatrixserverlib.Event, txnAndSessionID *api.TransactionID, authEventNIDs []types.EventNID) (types.RoomNID, types.StateAtEvent, error)
|
||||||
statements statements
|
StateEntriesForEventIDs(ctx context.Context, eventIDs []string) ([]types.StateEntry, error)
|
||||||
db *sql.DB
|
EventTypeNIDs(ctx context.Context, eventTypes []string) (map[string]types.EventTypeNID, error)
|
||||||
|
EventStateKeyNIDs(ctx context.Context, eventStateKeys []string) (map[string]types.EventStateKeyNID, error)
|
||||||
|
EventStateKeys(ctx context.Context, eventStateKeyNIDs []types.EventStateKeyNID) (map[types.EventStateKeyNID]string, error)
|
||||||
|
EventNIDs(ctx context.Context, eventIDs []string) (map[string]types.EventNID, error)
|
||||||
|
Events(ctx context.Context, eventNIDs []types.EventNID) ([]types.Event, error)
|
||||||
|
AddState(ctx context.Context, roomNID types.RoomNID, stateBlockNIDs []types.StateBlockNID, state []types.StateEntry) (types.StateSnapshotNID, error)
|
||||||
|
SetState(ctx context.Context, eventNID types.EventNID, stateNID types.StateSnapshotNID) error
|
||||||
|
StateAtEventIDs(ctx context.Context, eventIDs []string) ([]types.StateAtEvent, error)
|
||||||
|
StateBlockNIDs(ctx context.Context, stateNIDs []types.StateSnapshotNID) ([]types.StateBlockNIDList, error)
|
||||||
|
StateEntries(ctx context.Context, stateBlockNIDs []types.StateBlockNID) ([]types.StateEntryList, error)
|
||||||
|
SnapshotNIDFromEventID(ctx context.Context, eventID string) (types.StateSnapshotNID, error)
|
||||||
|
EventIDs(ctx context.Context, eventNIDs []types.EventNID) (map[types.EventNID]string, error)
|
||||||
|
GetLatestEventsForUpdate(ctx context.Context, roomNID types.RoomNID) (types.RoomRecentEventsUpdater, error)
|
||||||
|
GetTransactionEventID(ctx context.Context, transactionID string, sessionID int64, userID string) (string, error)
|
||||||
|
RoomNID(ctx context.Context, roomID string) (types.RoomNID, error)
|
||||||
|
LatestEventIDs(ctx context.Context, roomNID types.RoomNID) ([]gomatrixserverlib.EventReference, types.StateSnapshotNID, int64, error)
|
||||||
|
GetInvitesForUser(ctx context.Context, roomNID types.RoomNID, targetUserNID types.EventStateKeyNID) (senderUserIDs []types.EventStateKeyNID, err error)
|
||||||
|
SetRoomAlias(ctx context.Context, alias string, roomID string, creatorUserID string) error
|
||||||
|
GetRoomIDForAlias(ctx context.Context, alias string) (string, error)
|
||||||
|
GetAliasesForRoomID(ctx context.Context, roomID string) ([]string, error)
|
||||||
|
GetCreatorIDForAlias(ctx context.Context, alias string) (string, error)
|
||||||
|
RemoveRoomAlias(ctx context.Context, alias string) error
|
||||||
|
StateEntriesForTuples(ctx context.Context, stateBlockNIDs []types.StateBlockNID, stateKeyTuples []types.StateKeyTuple) ([]types.StateEntryList, error)
|
||||||
|
MembershipUpdater(ctx context.Context, roomID, targetUserID string) (types.MembershipUpdater, error)
|
||||||
|
GetMembership(ctx context.Context, roomNID types.RoomNID, requestSenderUserID string) (membershipEventNID types.EventNID, stillInRoom bool, err error)
|
||||||
|
GetMembershipEventNIDsForRoom(ctx context.Context, roomNID types.RoomNID, joinOnly bool) ([]types.EventNID, error)
|
||||||
|
EventsFromIDs(ctx context.Context, eventIDs []string) ([]types.Event, error)
|
||||||
|
GetRoomVersionForRoom(ctx context.Context, roomNID types.RoomNID) (int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open a postgres database.
|
// NewPublicRoomsServerDatabase opens a database connection.
|
||||||
func Open(dataSourceName string) (*Database, error) {
|
func Open(dataSourceName string) (Database, error) {
|
||||||
var d Database
|
uri, err := url.Parse(dataSourceName)
|
||||||
var err error
|
|
||||||
if d.db, err = sql.Open("postgres", dataSourceName); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err = d.statements.prepare(d.db); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreEvent implements input.EventDatabase
|
|
||||||
func (d *Database) StoreEvent(
|
|
||||||
ctx context.Context, event gomatrixserverlib.Event,
|
|
||||||
txnAndSessionID *api.TransactionID, authEventNIDs []types.EventNID,
|
|
||||||
) (types.RoomNID, types.StateAtEvent, error) {
|
|
||||||
var (
|
|
||||||
roomNID types.RoomNID
|
|
||||||
eventTypeNID types.EventTypeNID
|
|
||||||
eventStateKeyNID types.EventStateKeyNID
|
|
||||||
eventNID types.EventNID
|
|
||||||
stateNID types.StateSnapshotNID
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if txnAndSessionID != nil {
|
|
||||||
if err = d.statements.insertTransaction(
|
|
||||||
ctx, txnAndSessionID.TransactionID,
|
|
||||||
txnAndSessionID.SessionID, event.Sender(), event.EventID(),
|
|
||||||
); err != nil {
|
|
||||||
return 0, types.StateAtEvent{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if roomNID, err = d.assignRoomNID(ctx, nil, event.RoomID()); err != nil {
|
|
||||||
return 0, types.StateAtEvent{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if eventTypeNID, err = d.assignEventTypeNID(ctx, event.Type()); err != nil {
|
|
||||||
return 0, types.StateAtEvent{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
eventStateKey := event.StateKey()
|
|
||||||
// Assigned a numeric ID for the state_key if there is one present.
|
|
||||||
// Otherwise set the numeric ID for the state_key to 0.
|
|
||||||
if eventStateKey != nil {
|
|
||||||
if eventStateKeyNID, err = d.assignStateKeyNID(ctx, nil, *eventStateKey); err != nil {
|
|
||||||
return 0, types.StateAtEvent{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if eventNID, stateNID, err = d.statements.insertEvent(
|
|
||||||
ctx,
|
|
||||||
roomNID,
|
|
||||||
eventTypeNID,
|
|
||||||
eventStateKeyNID,
|
|
||||||
event.EventID(),
|
|
||||||
event.EventReference().EventSHA256,
|
|
||||||
authEventNIDs,
|
|
||||||
event.Depth(),
|
|
||||||
); err != nil {
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
// We've already inserted the event so select the numeric event ID
|
|
||||||
eventNID, stateNID, err = d.statements.selectEvent(ctx, event.EventID())
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return 0, types.StateAtEvent{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = d.statements.insertEventJSON(ctx, eventNID, event.JSON()); err != nil {
|
|
||||||
return 0, types.StateAtEvent{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return roomNID, types.StateAtEvent{
|
|
||||||
BeforeStateSnapshotNID: stateNID,
|
|
||||||
StateEntry: types.StateEntry{
|
|
||||||
StateKeyTuple: types.StateKeyTuple{
|
|
||||||
EventTypeNID: eventTypeNID,
|
|
||||||
EventStateKeyNID: eventStateKeyNID,
|
|
||||||
},
|
|
||||||
EventNID: eventNID,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Database) assignRoomNID(
|
|
||||||
ctx context.Context, txn *sql.Tx, roomID string,
|
|
||||||
) (types.RoomNID, error) {
|
|
||||||
// Check if we already have a numeric ID in the database.
|
|
||||||
roomNID, err := d.statements.selectRoomNID(ctx, txn, roomID)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
// We don't have a numeric ID so insert one into the database.
|
|
||||||
roomNID, err = d.statements.insertRoomNID(ctx, txn, roomID)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
// We raced with another insert so run the select again.
|
|
||||||
roomNID, err = d.statements.selectRoomNID(ctx, txn, roomID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return roomNID, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Database) assignEventTypeNID(
|
|
||||||
ctx context.Context, eventType string,
|
|
||||||
) (types.EventTypeNID, error) {
|
|
||||||
// Check if we already have a numeric ID in the database.
|
|
||||||
eventTypeNID, err := d.statements.selectEventTypeNID(ctx, eventType)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
// We don't have a numeric ID so insert one into the database.
|
|
||||||
eventTypeNID, err = d.statements.insertEventTypeNID(ctx, eventType)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
// We raced with another insert so run the select again.
|
|
||||||
eventTypeNID, err = d.statements.selectEventTypeNID(ctx, eventType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return eventTypeNID, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Database) assignStateKeyNID(
|
|
||||||
ctx context.Context, txn *sql.Tx, eventStateKey string,
|
|
||||||
) (types.EventStateKeyNID, error) {
|
|
||||||
// Check if we already have a numeric ID in the database.
|
|
||||||
eventStateKeyNID, err := d.statements.selectEventStateKeyNID(ctx, txn, eventStateKey)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
// We don't have a numeric ID so insert one into the database.
|
|
||||||
eventStateKeyNID, err = d.statements.insertEventStateKeyNID(ctx, txn, eventStateKey)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
// We raced with another insert so run the select again.
|
|
||||||
eventStateKeyNID, err = d.statements.selectEventStateKeyNID(ctx, txn, eventStateKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return eventStateKeyNID, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// StateEntriesForEventIDs implements input.EventDatabase
|
|
||||||
func (d *Database) StateEntriesForEventIDs(
|
|
||||||
ctx context.Context, eventIDs []string,
|
|
||||||
) ([]types.StateEntry, error) {
|
|
||||||
return d.statements.bulkSelectStateEventByID(ctx, eventIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventTypeNIDs implements state.RoomStateDatabase
|
|
||||||
func (d *Database) EventTypeNIDs(
|
|
||||||
ctx context.Context, eventTypes []string,
|
|
||||||
) (map[string]types.EventTypeNID, error) {
|
|
||||||
return d.statements.bulkSelectEventTypeNID(ctx, eventTypes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventStateKeyNIDs implements state.RoomStateDatabase
|
|
||||||
func (d *Database) EventStateKeyNIDs(
|
|
||||||
ctx context.Context, eventStateKeys []string,
|
|
||||||
) (map[string]types.EventStateKeyNID, error) {
|
|
||||||
return d.statements.bulkSelectEventStateKeyNID(ctx, eventStateKeys)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventStateKeys implements query.RoomserverQueryAPIDatabase
|
|
||||||
func (d *Database) EventStateKeys(
|
|
||||||
ctx context.Context, eventStateKeyNIDs []types.EventStateKeyNID,
|
|
||||||
) (map[types.EventStateKeyNID]string, error) {
|
|
||||||
return d.statements.bulkSelectEventStateKey(ctx, eventStateKeyNIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventNIDs implements query.RoomserverQueryAPIDatabase
|
|
||||||
func (d *Database) EventNIDs(
|
|
||||||
ctx context.Context, eventIDs []string,
|
|
||||||
) (map[string]types.EventNID, error) {
|
|
||||||
return d.statements.bulkSelectEventNID(ctx, eventIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Events implements input.EventDatabase
|
|
||||||
func (d *Database) Events(
|
|
||||||
ctx context.Context, eventNIDs []types.EventNID,
|
|
||||||
) ([]types.Event, error) {
|
|
||||||
eventJSONs, err := d.statements.bulkSelectEventJSON(ctx, eventNIDs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return postgres.Open(dataSourceName)
|
||||||
}
|
}
|
||||||
results := make([]types.Event, len(eventJSONs))
|
switch uri.Scheme {
|
||||||
for i, eventJSON := range eventJSONs {
|
case "postgres":
|
||||||
result := &results[i]
|
return postgres.Open(dataSourceName)
|
||||||
result.EventNID = eventJSON.EventNID
|
default:
|
||||||
// TODO: Use NewEventFromTrustedJSON for efficiency
|
return postgres.Open(dataSourceName)
|
||||||
result.Event, err = gomatrixserverlib.NewEventFromUntrustedJSON(eventJSON.EventJSON)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddState implements input.EventDatabase
|
|
||||||
func (d *Database) AddState(
|
|
||||||
ctx context.Context,
|
|
||||||
roomNID types.RoomNID,
|
|
||||||
stateBlockNIDs []types.StateBlockNID,
|
|
||||||
state []types.StateEntry,
|
|
||||||
) (types.StateSnapshotNID, error) {
|
|
||||||
if len(state) > 0 {
|
|
||||||
stateBlockNID, err := d.statements.selectNextStateBlockNID(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if err = d.statements.bulkInsertStateData(ctx, stateBlockNID, state); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
stateBlockNIDs = append(stateBlockNIDs[:len(stateBlockNIDs):len(stateBlockNIDs)], stateBlockNID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.statements.insertState(ctx, roomNID, stateBlockNIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetState implements input.EventDatabase
|
|
||||||
func (d *Database) SetState(
|
|
||||||
ctx context.Context, eventNID types.EventNID, stateNID types.StateSnapshotNID,
|
|
||||||
) error {
|
|
||||||
return d.statements.updateEventState(ctx, eventNID, stateNID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StateAtEventIDs implements input.EventDatabase
|
|
||||||
func (d *Database) StateAtEventIDs(
|
|
||||||
ctx context.Context, eventIDs []string,
|
|
||||||
) ([]types.StateAtEvent, error) {
|
|
||||||
return d.statements.bulkSelectStateAtEventByID(ctx, eventIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StateBlockNIDs implements state.RoomStateDatabase
|
|
||||||
func (d *Database) StateBlockNIDs(
|
|
||||||
ctx context.Context, stateNIDs []types.StateSnapshotNID,
|
|
||||||
) ([]types.StateBlockNIDList, error) {
|
|
||||||
return d.statements.bulkSelectStateBlockNIDs(ctx, stateNIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StateEntries implements state.RoomStateDatabase
|
|
||||||
func (d *Database) StateEntries(
|
|
||||||
ctx context.Context, stateBlockNIDs []types.StateBlockNID,
|
|
||||||
) ([]types.StateEntryList, error) {
|
|
||||||
return d.statements.bulkSelectStateBlockEntries(ctx, stateBlockNIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SnapshotNIDFromEventID implements state.RoomStateDatabase
|
|
||||||
func (d *Database) SnapshotNIDFromEventID(
|
|
||||||
ctx context.Context, eventID string,
|
|
||||||
) (types.StateSnapshotNID, error) {
|
|
||||||
_, stateNID, err := d.statements.selectEvent(ctx, eventID)
|
|
||||||
return stateNID, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventIDs implements input.RoomEventDatabase
|
|
||||||
func (d *Database) EventIDs(
|
|
||||||
ctx context.Context, eventNIDs []types.EventNID,
|
|
||||||
) (map[types.EventNID]string, error) {
|
|
||||||
return d.statements.bulkSelectEventID(ctx, eventNIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLatestEventsForUpdate implements input.EventDatabase
|
|
||||||
func (d *Database) GetLatestEventsForUpdate(
|
|
||||||
ctx context.Context, roomNID types.RoomNID,
|
|
||||||
) (types.RoomRecentEventsUpdater, error) {
|
|
||||||
txn, err := d.db.Begin()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
eventNIDs, lastEventNIDSent, currentStateSnapshotNID, err :=
|
|
||||||
d.statements.selectLatestEventsNIDsForUpdate(ctx, txn, roomNID)
|
|
||||||
if err != nil {
|
|
||||||
txn.Rollback() // nolint: errcheck
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
stateAndRefs, err := d.statements.bulkSelectStateAtEventAndReference(ctx, txn, eventNIDs)
|
|
||||||
if err != nil {
|
|
||||||
txn.Rollback() // nolint: errcheck
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var lastEventIDSent string
|
|
||||||
if lastEventNIDSent != 0 {
|
|
||||||
lastEventIDSent, err = d.statements.selectEventID(ctx, txn, lastEventNIDSent)
|
|
||||||
if err != nil {
|
|
||||||
txn.Rollback() // nolint: errcheck
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &roomRecentEventsUpdater{
|
|
||||||
transaction{ctx, txn}, d, roomNID, stateAndRefs, lastEventIDSent, currentStateSnapshotNID,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTransactionEventID implements input.EventDatabase
|
|
||||||
func (d *Database) GetTransactionEventID(
|
|
||||||
ctx context.Context, transactionID string,
|
|
||||||
sessionID int64, userID string,
|
|
||||||
) (string, error) {
|
|
||||||
eventID, err := d.statements.selectTransactionEventID(ctx, transactionID, sessionID, userID)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
return eventID, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type roomRecentEventsUpdater struct {
|
|
||||||
transaction
|
|
||||||
d *Database
|
|
||||||
roomNID types.RoomNID
|
|
||||||
latestEvents []types.StateAtEventAndReference
|
|
||||||
lastEventIDSent string
|
|
||||||
currentStateSnapshotNID types.StateSnapshotNID
|
|
||||||
}
|
|
||||||
|
|
||||||
// LatestEvents implements types.RoomRecentEventsUpdater
|
|
||||||
func (u *roomRecentEventsUpdater) LatestEvents() []types.StateAtEventAndReference {
|
|
||||||
return u.latestEvents
|
|
||||||
}
|
|
||||||
|
|
||||||
// LastEventIDSent implements types.RoomRecentEventsUpdater
|
|
||||||
func (u *roomRecentEventsUpdater) LastEventIDSent() string {
|
|
||||||
return u.lastEventIDSent
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurrentStateSnapshotNID implements types.RoomRecentEventsUpdater
|
|
||||||
func (u *roomRecentEventsUpdater) CurrentStateSnapshotNID() types.StateSnapshotNID {
|
|
||||||
return u.currentStateSnapshotNID
|
|
||||||
}
|
|
||||||
|
|
||||||
// StorePreviousEvents implements types.RoomRecentEventsUpdater
|
|
||||||
func (u *roomRecentEventsUpdater) StorePreviousEvents(eventNID types.EventNID, previousEventReferences []gomatrixserverlib.EventReference) error {
|
|
||||||
for _, ref := range previousEventReferences {
|
|
||||||
if err := u.d.statements.insertPreviousEvent(u.ctx, u.txn, ref.EventID, ref.EventSHA256, eventNID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsReferenced implements types.RoomRecentEventsUpdater
|
|
||||||
func (u *roomRecentEventsUpdater) IsReferenced(eventReference gomatrixserverlib.EventReference) (bool, error) {
|
|
||||||
err := u.d.statements.selectPreviousEventExists(u.ctx, u.txn, eventReference.EventID, eventReference.EventSHA256)
|
|
||||||
if err == nil {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLatestEvents implements types.RoomRecentEventsUpdater
|
|
||||||
func (u *roomRecentEventsUpdater) SetLatestEvents(
|
|
||||||
roomNID types.RoomNID, latest []types.StateAtEventAndReference, lastEventNIDSent types.EventNID,
|
|
||||||
currentStateSnapshotNID types.StateSnapshotNID,
|
|
||||||
) error {
|
|
||||||
eventNIDs := make([]types.EventNID, len(latest))
|
|
||||||
for i := range latest {
|
|
||||||
eventNIDs[i] = latest[i].EventNID
|
|
||||||
}
|
|
||||||
return u.d.statements.updateLatestEventNIDs(u.ctx, u.txn, roomNID, eventNIDs, lastEventNIDSent, currentStateSnapshotNID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasEventBeenSent implements types.RoomRecentEventsUpdater
|
|
||||||
func (u *roomRecentEventsUpdater) HasEventBeenSent(eventNID types.EventNID) (bool, error) {
|
|
||||||
return u.d.statements.selectEventSentToOutput(u.ctx, u.txn, eventNID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkEventAsSent implements types.RoomRecentEventsUpdater
|
|
||||||
func (u *roomRecentEventsUpdater) MarkEventAsSent(eventNID types.EventNID) error {
|
|
||||||
return u.d.statements.updateEventSentToOutput(u.ctx, u.txn, eventNID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *roomRecentEventsUpdater) MembershipUpdater(targetUserNID types.EventStateKeyNID) (types.MembershipUpdater, error) {
|
|
||||||
return u.d.membershipUpdaterTxn(u.ctx, u.txn, u.roomNID, targetUserNID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoomNID implements query.RoomserverQueryAPIDB
|
|
||||||
func (d *Database) RoomNID(ctx context.Context, roomID string) (types.RoomNID, error) {
|
|
||||||
roomNID, err := d.statements.selectRoomNID(ctx, nil, roomID)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
return roomNID, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// LatestEventIDs implements query.RoomserverQueryAPIDatabase
|
|
||||||
func (d *Database) LatestEventIDs(
|
|
||||||
ctx context.Context, roomNID types.RoomNID,
|
|
||||||
) ([]gomatrixserverlib.EventReference, types.StateSnapshotNID, int64, error) {
|
|
||||||
eventNIDs, currentStateSnapshotNID, err := d.statements.selectLatestEventNIDs(ctx, roomNID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, 0, err
|
|
||||||
}
|
|
||||||
references, err := d.statements.bulkSelectEventReference(ctx, eventNIDs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, 0, err
|
|
||||||
}
|
|
||||||
depth, err := d.statements.selectMaxEventDepth(ctx, eventNIDs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, 0, err
|
|
||||||
}
|
|
||||||
return references, currentStateSnapshotNID, depth, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInvitesForUser implements query.RoomserverQueryAPIDatabase
|
|
||||||
func (d *Database) GetInvitesForUser(
|
|
||||||
ctx context.Context,
|
|
||||||
roomNID types.RoomNID,
|
|
||||||
targetUserNID types.EventStateKeyNID,
|
|
||||||
) (senderUserIDs []types.EventStateKeyNID, err error) {
|
|
||||||
return d.statements.selectInviteActiveForUserInRoom(ctx, targetUserNID, roomNID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRoomAlias implements alias.RoomserverAliasAPIDB
|
|
||||||
func (d *Database) SetRoomAlias(ctx context.Context, alias string, roomID string, creatorUserID string) error {
|
|
||||||
return d.statements.insertRoomAlias(ctx, alias, roomID, creatorUserID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRoomIDForAlias implements alias.RoomserverAliasAPIDB
|
|
||||||
func (d *Database) GetRoomIDForAlias(ctx context.Context, alias string) (string, error) {
|
|
||||||
return d.statements.selectRoomIDFromAlias(ctx, alias)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAliasesForRoomID implements alias.RoomserverAliasAPIDB
|
|
||||||
func (d *Database) GetAliasesForRoomID(ctx context.Context, roomID string) ([]string, error) {
|
|
||||||
return d.statements.selectAliasesFromRoomID(ctx, roomID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCreatorIDForAlias implements alias.RoomserverAliasAPIDB
|
|
||||||
func (d *Database) GetCreatorIDForAlias(
|
|
||||||
ctx context.Context, alias string,
|
|
||||||
) (string, error) {
|
|
||||||
return d.statements.selectCreatorIDFromAlias(ctx, alias)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveRoomAlias implements alias.RoomserverAliasAPIDB
|
|
||||||
func (d *Database) RemoveRoomAlias(ctx context.Context, alias string) error {
|
|
||||||
return d.statements.deleteRoomAlias(ctx, alias)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StateEntriesForTuples implements state.RoomStateDatabase
|
|
||||||
func (d *Database) StateEntriesForTuples(
|
|
||||||
ctx context.Context,
|
|
||||||
stateBlockNIDs []types.StateBlockNID,
|
|
||||||
stateKeyTuples []types.StateKeyTuple,
|
|
||||||
) ([]types.StateEntryList, error) {
|
|
||||||
return d.statements.bulkSelectFilteredStateBlockEntries(
|
|
||||||
ctx, stateBlockNIDs, stateKeyTuples,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MembershipUpdater implements input.RoomEventDatabase
|
|
||||||
func (d *Database) MembershipUpdater(
|
|
||||||
ctx context.Context, roomID, targetUserID string,
|
|
||||||
) (types.MembershipUpdater, error) {
|
|
||||||
txn, err := d.db.Begin()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
succeeded := false
|
|
||||||
defer func() {
|
|
||||||
if !succeeded {
|
|
||||||
txn.Rollback() // nolint: errcheck
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
roomNID, err := d.assignRoomNID(ctx, txn, roomID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
targetUserNID, err := d.assignStateKeyNID(ctx, txn, targetUserID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
updater, err := d.membershipUpdaterTxn(ctx, txn, roomNID, targetUserNID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
succeeded = true
|
|
||||||
return updater, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type membershipUpdater struct {
|
|
||||||
transaction
|
|
||||||
d *Database
|
|
||||||
roomNID types.RoomNID
|
|
||||||
targetUserNID types.EventStateKeyNID
|
|
||||||
membership membershipState
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Database) membershipUpdaterTxn(
|
|
||||||
ctx context.Context,
|
|
||||||
txn *sql.Tx,
|
|
||||||
roomNID types.RoomNID,
|
|
||||||
targetUserNID types.EventStateKeyNID,
|
|
||||||
) (types.MembershipUpdater, error) {
|
|
||||||
|
|
||||||
if err := d.statements.insertMembership(ctx, txn, roomNID, targetUserNID); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
membership, err := d.statements.selectMembershipForUpdate(ctx, txn, roomNID, targetUserNID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &membershipUpdater{
|
|
||||||
transaction{ctx, txn}, d, roomNID, targetUserNID, membership,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsInvite implements types.MembershipUpdater
|
|
||||||
func (u *membershipUpdater) IsInvite() bool {
|
|
||||||
return u.membership == membershipStateInvite
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsJoin implements types.MembershipUpdater
|
|
||||||
func (u *membershipUpdater) IsJoin() bool {
|
|
||||||
return u.membership == membershipStateJoin
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsLeave implements types.MembershipUpdater
|
|
||||||
func (u *membershipUpdater) IsLeave() bool {
|
|
||||||
return u.membership == membershipStateLeaveOrBan
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetToInvite implements types.MembershipUpdater
|
|
||||||
func (u *membershipUpdater) SetToInvite(event gomatrixserverlib.Event) (bool, error) {
|
|
||||||
senderUserNID, err := u.d.assignStateKeyNID(u.ctx, u.txn, event.Sender())
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
inserted, err := u.d.statements.insertInviteEvent(
|
|
||||||
u.ctx, u.txn, event.EventID(), u.roomNID, u.targetUserNID, senderUserNID, event.JSON(),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if u.membership != membershipStateInvite {
|
|
||||||
if err = u.d.statements.updateMembership(
|
|
||||||
u.ctx, u.txn, u.roomNID, u.targetUserNID, senderUserNID, membershipStateInvite, 0,
|
|
||||||
); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return inserted, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetToJoin implements types.MembershipUpdater
|
|
||||||
func (u *membershipUpdater) SetToJoin(senderUserID string, eventID string, isUpdate bool) ([]string, error) {
|
|
||||||
var inviteEventIDs []string
|
|
||||||
|
|
||||||
senderUserNID, err := u.d.assignStateKeyNID(u.ctx, u.txn, senderUserID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is a join event update, there is no invite to update
|
|
||||||
if !isUpdate {
|
|
||||||
inviteEventIDs, err = u.d.statements.updateInviteRetired(
|
|
||||||
u.ctx, u.txn, u.roomNID, u.targetUserNID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look up the NID of the new join event
|
|
||||||
nIDs, err := u.d.EventNIDs(u.ctx, []string{eventID})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.membership != membershipStateJoin || isUpdate {
|
|
||||||
if err = u.d.statements.updateMembership(
|
|
||||||
u.ctx, u.txn, u.roomNID, u.targetUserNID, senderUserNID,
|
|
||||||
membershipStateJoin, nIDs[eventID],
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return inviteEventIDs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetToLeave implements types.MembershipUpdater
|
|
||||||
func (u *membershipUpdater) SetToLeave(senderUserID string, eventID string) ([]string, error) {
|
|
||||||
senderUserNID, err := u.d.assignStateKeyNID(u.ctx, u.txn, senderUserID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
inviteEventIDs, err := u.d.statements.updateInviteRetired(
|
|
||||||
u.ctx, u.txn, u.roomNID, u.targetUserNID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look up the NID of the new leave event
|
|
||||||
nIDs, err := u.d.EventNIDs(u.ctx, []string{eventID})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.membership != membershipStateLeaveOrBan {
|
|
||||||
if err = u.d.statements.updateMembership(
|
|
||||||
u.ctx, u.txn, u.roomNID, u.targetUserNID, senderUserNID,
|
|
||||||
membershipStateLeaveOrBan, nIDs[eventID],
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return inviteEventIDs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMembership implements query.RoomserverQueryAPIDB
|
|
||||||
func (d *Database) GetMembership(
|
|
||||||
ctx context.Context, roomNID types.RoomNID, requestSenderUserID string,
|
|
||||||
) (membershipEventNID types.EventNID, stillInRoom bool, err error) {
|
|
||||||
requestSenderUserNID, err := d.assignStateKeyNID(ctx, nil, requestSenderUserID)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
senderMembershipEventNID, senderMembership, err :=
|
|
||||||
d.statements.selectMembershipFromRoomAndTarget(
|
|
||||||
ctx, roomNID, requestSenderUserNID,
|
|
||||||
)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
// The user has never been a member of that room
|
|
||||||
return 0, false, nil
|
|
||||||
} else if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return senderMembershipEventNID, senderMembership == membershipStateJoin, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMembershipEventNIDsForRoom implements query.RoomserverQueryAPIDB
|
|
||||||
func (d *Database) GetMembershipEventNIDsForRoom(
|
|
||||||
ctx context.Context, roomNID types.RoomNID, joinOnly bool,
|
|
||||||
) ([]types.EventNID, error) {
|
|
||||||
if joinOnly {
|
|
||||||
return d.statements.selectMembershipsFromRoomAndMembership(
|
|
||||||
ctx, roomNID, membershipStateJoin,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.statements.selectMembershipsFromRoom(ctx, roomNID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventsFromIDs implements query.RoomserverQueryAPIEventDB
|
|
||||||
func (d *Database) EventsFromIDs(ctx context.Context, eventIDs []string) ([]types.Event, error) {
|
|
||||||
nidMap, err := d.EventNIDs(ctx, eventIDs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var nids []types.EventNID
|
|
||||||
for _, nid := range nidMap {
|
|
||||||
nids = append(nids, nid)
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.Events(ctx, nids)
|
|
||||||
}
|
|
||||||
|
|
||||||
type transaction struct {
|
|
||||||
ctx context.Context
|
|
||||||
txn *sql.Tx
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit implements types.Transaction
|
|
||||||
func (t *transaction) Commit() error {
|
|
||||||
return t.txn.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rollback implements types.Transaction
|
|
||||||
func (t *transaction) Rollback() error {
|
|
||||||
return t.txn.Rollback()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
112
roomserver/version/version.go
Normal file
112
roomserver/version/version.go
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RoomVersionID int
|
||||||
|
type EventFormatID int
|
||||||
|
|
||||||
|
const (
|
||||||
|
RoomVersionV1 RoomVersionID = iota + 1
|
||||||
|
RoomVersionV2
|
||||||
|
RoomVersionV3
|
||||||
|
RoomVersionV4
|
||||||
|
RoomVersionV5
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EventFormatV1 EventFormatID = iota + 1 // original event ID formatting
|
||||||
|
EventFormatV2 // event ID is event hash
|
||||||
|
EventFormatV3 // event ID is URL-safe base64 event hash
|
||||||
|
)
|
||||||
|
|
||||||
|
type RoomVersionDescription struct {
|
||||||
|
Supported bool
|
||||||
|
Stable bool
|
||||||
|
StateResolution state.StateResolutionVersion
|
||||||
|
EventFormat EventFormatID
|
||||||
|
EnforceSigningKeyValidity bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var roomVersions = map[RoomVersionID]RoomVersionDescription{
|
||||||
|
RoomVersionV1: RoomVersionDescription{
|
||||||
|
Supported: true,
|
||||||
|
Stable: true,
|
||||||
|
StateResolution: state.StateResolutionAlgorithmV1,
|
||||||
|
EventFormat: EventFormatV1,
|
||||||
|
EnforceSigningKeyValidity: false,
|
||||||
|
},
|
||||||
|
RoomVersionV2: RoomVersionDescription{
|
||||||
|
Supported: false,
|
||||||
|
Stable: true,
|
||||||
|
StateResolution: state.StateResolutionAlgorithmV2,
|
||||||
|
EventFormat: EventFormatV1,
|
||||||
|
EnforceSigningKeyValidity: false,
|
||||||
|
},
|
||||||
|
RoomVersionV3: RoomVersionDescription{
|
||||||
|
Supported: false,
|
||||||
|
Stable: true,
|
||||||
|
StateResolution: state.StateResolutionAlgorithmV2,
|
||||||
|
EventFormat: EventFormatV2,
|
||||||
|
EnforceSigningKeyValidity: false,
|
||||||
|
},
|
||||||
|
RoomVersionV4: RoomVersionDescription{
|
||||||
|
Supported: false,
|
||||||
|
Stable: true,
|
||||||
|
StateResolution: state.StateResolutionAlgorithmV2,
|
||||||
|
EventFormat: EventFormatV3,
|
||||||
|
EnforceSigningKeyValidity: false,
|
||||||
|
},
|
||||||
|
RoomVersionV5: RoomVersionDescription{
|
||||||
|
Supported: false,
|
||||||
|
Stable: true,
|
||||||
|
StateResolution: state.StateResolutionAlgorithmV2,
|
||||||
|
EventFormat: EventFormatV3,
|
||||||
|
EnforceSigningKeyValidity: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDefaultRoomVersion() RoomVersionID {
|
||||||
|
return RoomVersionV1
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRoomVersions() map[RoomVersionID]RoomVersionDescription {
|
||||||
|
return roomVersions
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSupportedRoomVersions() map[RoomVersionID]RoomVersionDescription {
|
||||||
|
versions := make(map[RoomVersionID]RoomVersionDescription)
|
||||||
|
for id, version := range GetRoomVersions() {
|
||||||
|
if version.Supported {
|
||||||
|
versions[id] = version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return versions
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSupportedRoomVersion(version RoomVersionID) (desc RoomVersionDescription, err error) {
|
||||||
|
if version, ok := roomVersions[version]; ok {
|
||||||
|
desc = version
|
||||||
|
}
|
||||||
|
if !desc.Supported {
|
||||||
|
err = errors.New("unsupported room version")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -29,8 +29,15 @@ echo "Installing golangci-lint..."
|
||||||
cp go.mod go.mod.bak && cp go.sum go.sum.bak
|
cp go.mod go.mod.bak && cp go.sum go.sum.bak
|
||||||
go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.19.1
|
go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.19.1
|
||||||
|
|
||||||
|
# Run linting
|
||||||
echo "Looking for lint..."
|
echo "Looking for lint..."
|
||||||
golangci-lint run $args
|
|
||||||
|
# Capture exit code to ensure go.{mod,sum} is restored before exiting
|
||||||
|
exit_code=0
|
||||||
|
|
||||||
|
golangci-lint run $args || exit_code=1
|
||||||
|
|
||||||
# Restore go.{mod,sum}
|
# Restore go.{mod,sum}
|
||||||
mv go.mod.bak go.mod && mv go.sum.bak go.sum
|
mv go.mod.bak go.mod && mv go.sum.bak go.sum
|
||||||
|
|
||||||
|
exit $exit_code
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,89 @@
|
||||||
#! /bin/bash
|
#! /bin/bash
|
||||||
|
#
|
||||||
|
# Parses a results.tap file from SyTest output and a file containing test names (a test whitelist)
|
||||||
|
# and checks whether a test name that exists in the whitelist (that should pass), failed or not.
|
||||||
|
#
|
||||||
|
# An optional blacklist file can be added, also containing test names, where if a test name is
|
||||||
|
# present, the script will not error even if the test is in the whitelist file and failed
|
||||||
|
#
|
||||||
|
# For each of these files, lines starting with '#' are ignored.
|
||||||
|
#
|
||||||
|
# Usage ./show-expected-fail-tests.sh results.tap whitelist [blacklist]
|
||||||
|
|
||||||
results_file=$1
|
results_file=$1
|
||||||
testfile=$2
|
whitelist_file=$2
|
||||||
|
blacklist_file=$3
|
||||||
|
|
||||||
fail_build=0
|
fail_build=0
|
||||||
|
|
||||||
|
if [ $# -lt 2 ]; then
|
||||||
|
echo "Usage: $0 results.tap whitelist [blacklist]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [ ! -f "$results_file" ]; then
|
if [ ! -f "$results_file" ]; then
|
||||||
echo "ERROR: Specified results file ${results_file} doesn't exist."
|
echo "ERROR: Specified results file '${results_file}' doesn't exist."
|
||||||
fail_build=1
|
fail_build=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -f "$testfile" ]; then
|
if [ ! -f "$whitelist_file" ]; then
|
||||||
echo "ERROR: Specified testfile ${testfile} doesn't exist."
|
echo "ERROR: Specified test whitelist '${whitelist_file}' doesn't exist."
|
||||||
fail_build=1
|
fail_build=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
blacklisted_tests=()
|
||||||
|
|
||||||
|
# Check if a blacklist file was provided
|
||||||
|
if [ $# -eq 3 ]; then
|
||||||
|
# Read test blacklist file
|
||||||
|
if [ ! -f "$blacklist_file" ]; then
|
||||||
|
echo "ERROR: Specified test blacklist file '${blacklist_file}' doesn't exist."
|
||||||
|
fail_build=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read each line, ignoring those that start with '#'
|
||||||
|
blacklisted_tests=""
|
||||||
|
search_non_comments=$(grep -v '^#' ${blacklist_file})
|
||||||
|
while read -r line ; do
|
||||||
|
# Record the blacklisted test name
|
||||||
|
blacklisted_tests+=("${line}")
|
||||||
|
done <<< "${search_non_comments}" # This allows us to edit blacklisted_tests in the while loop
|
||||||
|
fi
|
||||||
|
|
||||||
[ "$fail_build" = 0 ] || exit 1
|
[ "$fail_build" = 0 ] || exit 1
|
||||||
|
|
||||||
passed_but_expected_fail=$(grep ' # TODO passed but expected fail' ${results_file} | sed -E 's/^ok [0-9]+ (\(expected fail\) )?//' | sed -E 's/( \([0-9]+ subtests\))? # TODO passed but expected fail$//')
|
passed_but_expected_fail=$(grep ' # TODO passed but expected fail' ${results_file} | sed -E 's/^ok [0-9]+ (\(expected fail\) )?//' | sed -E 's/( \([0-9]+ subtests\))? # TODO passed but expected fail$//')
|
||||||
tests_to_add=""
|
tests_to_add=""
|
||||||
already_in_testfile=""
|
already_in_whitelist=""
|
||||||
|
|
||||||
while read -r test_id; do
|
while read -r test_name; do
|
||||||
[ "${test_id}" = "" ] && continue
|
# Ignore empty lines
|
||||||
grep "${test_id}" "${testfile}" > /dev/null 2>&1
|
[ "${test_name}" = "" ] && continue
|
||||||
|
|
||||||
|
grep "${test_name}" "${whitelist_file}" > /dev/null 2>&1
|
||||||
if [ "$?" != "0" ]; then
|
if [ "$?" != "0" ]; then
|
||||||
tests_to_add="${tests_to_add}${test_id}\n"
|
# Check if this test name is blacklisted
|
||||||
|
if printf '%s\n' "${blacklisted_tests[@]}" | grep -q -P "^${test_name}$"; then
|
||||||
|
# Don't notify about this test
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Append this test_name to the existing list
|
||||||
|
tests_to_add="${tests_to_add}${test_name}\n"
|
||||||
fail_build=1
|
fail_build=1
|
||||||
else
|
else
|
||||||
already_in_testfile="${already_in_testfile}${test_id}\n"
|
already_in_whitelist="${already_in_whitelist}${test_name}\n"
|
||||||
fi
|
fi
|
||||||
done <<< "${passed_but_expected_fail}"
|
done <<< "${passed_but_expected_fail}"
|
||||||
|
|
||||||
if [ -n "${tests_to_add}" ]; then
|
if [ -n "${tests_to_add}" ]; then
|
||||||
echo "ERROR: The following passed tests are not present in testfile. Please append them to the file:"
|
echo "ERROR: The following passed tests are not present in $2. Please append them to the file:"
|
||||||
echo -e "${tests_to_add}"
|
echo -e "${tests_to_add}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "${already_in_testfile}" ]; then
|
if [ -n "${already_in_whitelist}" ]; then
|
||||||
echo "WARN: Tests in testfile still marked as expected fail:"
|
echo "WARN: Tests in the whitelist still marked as expected fail:"
|
||||||
echo -e "${already_in_testfile}"
|
echo -e "${already_in_whitelist}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exit ${fail_build}
|
exit ${fail_build}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import (
|
||||||
// OutputClientDataConsumer consumes events that originated in the client API server.
|
// OutputClientDataConsumer consumes events that originated in the client API server.
|
||||||
type OutputClientDataConsumer struct {
|
type OutputClientDataConsumer struct {
|
||||||
clientAPIConsumer *common.ContinualConsumer
|
clientAPIConsumer *common.ContinualConsumer
|
||||||
db *storage.SyncServerDatasource
|
db storage.Database
|
||||||
notifier *sync.Notifier
|
notifier *sync.Notifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ func NewOutputClientDataConsumer(
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
kafkaConsumer sarama.Consumer,
|
kafkaConsumer sarama.Consumer,
|
||||||
n *sync.Notifier,
|
n *sync.Notifier,
|
||||||
store *storage.SyncServerDatasource,
|
store storage.Database,
|
||||||
) *OutputClientDataConsumer {
|
) *OutputClientDataConsumer {
|
||||||
|
|
||||||
consumer := common.ContinualConsumer{
|
consumer := common.ContinualConsumer{
|
||||||
|
|
@ -90,7 +90,7 @@ func (s *OutputClientDataConsumer) onMessage(msg *sarama.ConsumerMessage) error
|
||||||
}).Panicf("could not save account data")
|
}).Panicf("could not save account data")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.notifier.OnNewEvent(nil, "", []string{string(msg.Key)}, types.SyncPosition{PDUPosition: pduPos})
|
s.notifier.OnNewEvent(nil, "", []string{string(msg.Key)}, types.PaginationToken{PDUPosition: pduPos})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ import (
|
||||||
// OutputRoomEventConsumer consumes events that originated in the room server.
|
// OutputRoomEventConsumer consumes events that originated in the room server.
|
||||||
type OutputRoomEventConsumer struct {
|
type OutputRoomEventConsumer struct {
|
||||||
roomServerConsumer *common.ContinualConsumer
|
roomServerConsumer *common.ContinualConsumer
|
||||||
db *storage.SyncServerDatasource
|
db storage.Database
|
||||||
notifier *sync.Notifier
|
notifier *sync.Notifier
|
||||||
query api.RoomserverQueryAPI
|
query api.RoomserverQueryAPI
|
||||||
}
|
}
|
||||||
|
|
@ -43,7 +43,7 @@ func NewOutputRoomEventConsumer(
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
kafkaConsumer sarama.Consumer,
|
kafkaConsumer sarama.Consumer,
|
||||||
n *sync.Notifier,
|
n *sync.Notifier,
|
||||||
store *storage.SyncServerDatasource,
|
store storage.Database,
|
||||||
queryAPI api.RoomserverQueryAPI,
|
queryAPI api.RoomserverQueryAPI,
|
||||||
) *OutputRoomEventConsumer {
|
) *OutputRoomEventConsumer {
|
||||||
|
|
||||||
|
|
@ -133,6 +133,7 @@ func (s *OutputRoomEventConsumer) onNewRoomEvent(
|
||||||
msg.AddsStateEventIDs,
|
msg.AddsStateEventIDs,
|
||||||
msg.RemovesStateEventIDs,
|
msg.RemovesStateEventIDs,
|
||||||
msg.TransactionID,
|
msg.TransactionID,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// panic rather than continue with an inconsistent database
|
// panic rather than continue with an inconsistent database
|
||||||
|
|
@ -144,7 +145,7 @@ func (s *OutputRoomEventConsumer) onNewRoomEvent(
|
||||||
}).Panicf("roomserver output log: write event failure")
|
}).Panicf("roomserver output log: write event failure")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
s.notifier.OnNewEvent(&ev, "", nil, types.SyncPosition{PDUPosition: pduPos})
|
s.notifier.OnNewEvent(&ev, "", nil, types.PaginationToken{PDUPosition: pduPos})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +162,7 @@ func (s *OutputRoomEventConsumer) onNewInviteEvent(
|
||||||
}).Panicf("roomserver output log: write invite failure")
|
}).Panicf("roomserver output log: write invite failure")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
s.notifier.OnNewEvent(&msg.Event, "", nil, types.SyncPosition{PDUPosition: pduPos})
|
s.notifier.OnNewEvent(&msg.Event, "", nil, types.PaginationToken{PDUPosition: pduPos})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import (
|
||||||
// OutputTypingEventConsumer consumes events that originated in the typing server.
|
// OutputTypingEventConsumer consumes events that originated in the typing server.
|
||||||
type OutputTypingEventConsumer struct {
|
type OutputTypingEventConsumer struct {
|
||||||
typingConsumer *common.ContinualConsumer
|
typingConsumer *common.ContinualConsumer
|
||||||
db *storage.SyncServerDatasource
|
db storage.Database
|
||||||
notifier *sync.Notifier
|
notifier *sync.Notifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ func NewOutputTypingEventConsumer(
|
||||||
cfg *config.Dendrite,
|
cfg *config.Dendrite,
|
||||||
kafkaConsumer sarama.Consumer,
|
kafkaConsumer sarama.Consumer,
|
||||||
n *sync.Notifier,
|
n *sync.Notifier,
|
||||||
store *storage.SyncServerDatasource,
|
store storage.Database,
|
||||||
) *OutputTypingEventConsumer {
|
) *OutputTypingEventConsumer {
|
||||||
|
|
||||||
consumer := common.ContinualConsumer{
|
consumer := common.ContinualConsumer{
|
||||||
|
|
@ -63,7 +63,12 @@ func NewOutputTypingEventConsumer(
|
||||||
// Start consuming from typing api
|
// Start consuming from typing api
|
||||||
func (s *OutputTypingEventConsumer) Start() error {
|
func (s *OutputTypingEventConsumer) Start() error {
|
||||||
s.db.SetTypingTimeoutCallback(func(userID, roomID string, latestSyncPosition int64) {
|
s.db.SetTypingTimeoutCallback(func(userID, roomID string, latestSyncPosition int64) {
|
||||||
s.notifier.OnNewEvent(nil, roomID, nil, types.SyncPosition{TypingPosition: latestSyncPosition})
|
s.notifier.OnNewEvent(
|
||||||
|
nil, roomID, nil,
|
||||||
|
types.PaginationToken{
|
||||||
|
EDUTypingPosition: types.StreamPosition(latestSyncPosition),
|
||||||
|
},
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return s.typingConsumer.Start()
|
return s.typingConsumer.Start()
|
||||||
|
|
@ -83,7 +88,7 @@ func (s *OutputTypingEventConsumer) onMessage(msg *sarama.ConsumerMessage) error
|
||||||
"typing": output.Event.Typing,
|
"typing": output.Event.Typing,
|
||||||
}).Debug("received data from typing server")
|
}).Debug("received data from typing server")
|
||||||
|
|
||||||
var typingPos int64
|
var typingPos types.StreamPosition
|
||||||
typingEvent := output.Event
|
typingEvent := output.Event
|
||||||
if typingEvent.Typing {
|
if typingEvent.Typing {
|
||||||
typingPos = s.db.AddTypingUser(typingEvent.UserID, typingEvent.RoomID, output.ExpireTime)
|
typingPos = s.db.AddTypingUser(typingEvent.UserID, typingEvent.RoomID, output.ExpireTime)
|
||||||
|
|
@ -91,6 +96,6 @@ func (s *OutputTypingEventConsumer) onMessage(msg *sarama.ConsumerMessage) error
|
||||||
typingPos = s.db.RemoveTypingUser(typingEvent.UserID, typingEvent.RoomID)
|
typingPos = s.db.RemoveTypingUser(typingEvent.UserID, typingEvent.RoomID)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.notifier.OnNewEvent(nil, output.Event.RoomID, nil, types.SyncPosition{TypingPosition: typingPos})
|
s.notifier.OnNewEvent(nil, output.Event.RoomID, nil, types.PaginationToken{EDUTypingPosition: typingPos})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
487
syncapi/routing/messages.go
Normal file
487
syncapi/routing/messages.go
Normal file
|
|
@ -0,0 +1,487 @@
|
||||||
|
// Copyright 2018 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package routing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||||
|
"github.com/matrix-org/dendrite/syncapi/types"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type messagesReq struct {
|
||||||
|
ctx context.Context
|
||||||
|
db storage.Database
|
||||||
|
queryAPI api.RoomserverQueryAPI
|
||||||
|
federation *gomatrixserverlib.FederationClient
|
||||||
|
cfg *config.Dendrite
|
||||||
|
roomID string
|
||||||
|
from *types.PaginationToken
|
||||||
|
to *types.PaginationToken
|
||||||
|
wasToProvided bool
|
||||||
|
limit int
|
||||||
|
backwardOrdering bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type messagesResp struct {
|
||||||
|
Start string `json:"start"`
|
||||||
|
End string `json:"end"`
|
||||||
|
Chunk []gomatrixserverlib.ClientEvent `json:"chunk"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultMessagesLimit = 10
|
||||||
|
|
||||||
|
// OnIncomingMessagesRequest implements the /messages endpoint from the
|
||||||
|
// client-server API.
|
||||||
|
// See: https://matrix.org/docs/spec/client_server/latest.html#get-matrix-client-r0-rooms-roomid-messages
|
||||||
|
func OnIncomingMessagesRequest(
|
||||||
|
req *http.Request, db storage.Database, roomID string,
|
||||||
|
federation *gomatrixserverlib.FederationClient,
|
||||||
|
queryAPI api.RoomserverQueryAPI,
|
||||||
|
cfg *config.Dendrite,
|
||||||
|
) util.JSONResponse {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Extract parameters from the request's URL.
|
||||||
|
// Pagination tokens.
|
||||||
|
from, err := types.NewPaginationTokenFromString(req.URL.Query().Get("from"))
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.InvalidArgumentValue("Invalid from parameter: " + err.Error()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direction to return events from.
|
||||||
|
dir := req.URL.Query().Get("dir")
|
||||||
|
if dir != "b" && dir != "f" {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.MissingArgument("Bad or missing dir query parameter (should be either 'b' or 'f')"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A boolean is easier to handle in this case, especially since dir is sure
|
||||||
|
// to have one of the two accepted values (so dir == "f" <=> !backwardOrdering).
|
||||||
|
backwardOrdering := (dir == "b")
|
||||||
|
|
||||||
|
// Pagination tokens. To is optional, and its default value depends on the
|
||||||
|
// direction ("b" or "f").
|
||||||
|
var to *types.PaginationToken
|
||||||
|
wasToProvided := true
|
||||||
|
if s := req.URL.Query().Get("to"); len(s) > 0 {
|
||||||
|
to, err = types.NewPaginationTokenFromString(s)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.InvalidArgumentValue("Invalid to parameter: " + err.Error()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If "to" isn't provided, it defaults to either the earliest stream
|
||||||
|
// position (if we're going backward) or to the latest one (if we're
|
||||||
|
// going forward).
|
||||||
|
to, err = setToDefault(req.Context(), db, backwardOrdering, roomID)
|
||||||
|
if err != nil {
|
||||||
|
return httputil.LogThenError(req, err)
|
||||||
|
}
|
||||||
|
wasToProvided = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maximum number of events to return; defaults to 10.
|
||||||
|
limit := defaultMessagesLimit
|
||||||
|
if len(req.URL.Query().Get("limit")) > 0 {
|
||||||
|
limit, err = strconv.Atoi(req.URL.Query().Get("limit"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.InvalidArgumentValue("limit could not be parsed into an integer: " + err.Error()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: Implement filtering (#587)
|
||||||
|
|
||||||
|
// Check the room ID's format.
|
||||||
|
if _, _, err = gomatrixserverlib.SplitID('!', roomID); err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.MissingArgument("Bad room ID: " + err.Error()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mReq := messagesReq{
|
||||||
|
ctx: req.Context(),
|
||||||
|
db: db,
|
||||||
|
queryAPI: queryAPI,
|
||||||
|
federation: federation,
|
||||||
|
cfg: cfg,
|
||||||
|
roomID: roomID,
|
||||||
|
from: from,
|
||||||
|
to: to,
|
||||||
|
wasToProvided: wasToProvided,
|
||||||
|
limit: limit,
|
||||||
|
backwardOrdering: backwardOrdering,
|
||||||
|
}
|
||||||
|
|
||||||
|
clientEvents, start, end, err := mReq.retrieveEvents()
|
||||||
|
if err != nil {
|
||||||
|
return httputil.LogThenError(req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Respond with the events.
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
JSON: messagesResp{
|
||||||
|
Chunk: clientEvents,
|
||||||
|
Start: start.String(),
|
||||||
|
End: end.String(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieveEvents retrieve events from the local database for a request on
|
||||||
|
// /messages. If there's not enough events to retrieve, it asks another
|
||||||
|
// homeserver in the room for older events.
|
||||||
|
// Returns an error if there was an issue talking to the database or with the
|
||||||
|
// remote homeserver.
|
||||||
|
func (r *messagesReq) retrieveEvents() (
|
||||||
|
clientEvents []gomatrixserverlib.ClientEvent, start,
|
||||||
|
end *types.PaginationToken, err error,
|
||||||
|
) {
|
||||||
|
// Retrieve the events from the local database.
|
||||||
|
streamEvents, err := r.db.GetEventsInRange(
|
||||||
|
r.ctx, r.from, r.to, r.roomID, r.limit, r.backwardOrdering,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var events []gomatrixserverlib.Event
|
||||||
|
|
||||||
|
// There can be two reasons for streamEvents to be empty: either we've
|
||||||
|
// reached the oldest event in the room (or the most recent one, depending
|
||||||
|
// on the ordering), or we've reached a backward extremity.
|
||||||
|
if len(streamEvents) == 0 {
|
||||||
|
if events, err = r.handleEmptyEventsSlice(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if events, err = r.handleNonEmptyEventsSlice(streamEvents); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't get any event, we don't need to proceed any further.
|
||||||
|
if len(events) == 0 {
|
||||||
|
return []gomatrixserverlib.ClientEvent{}, r.from, r.to, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the events to ensure we send them in the right order. We currently
|
||||||
|
// do that based on the event's timestamp.
|
||||||
|
if r.backwardOrdering {
|
||||||
|
sort.SliceStable(events, func(i int, j int) bool {
|
||||||
|
// Backward ordering is antichronological (latest event to oldest
|
||||||
|
// one).
|
||||||
|
return sortEvents(&(events[j]), &(events[i]))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
sort.SliceStable(events, func(i int, j int) bool {
|
||||||
|
// Forward ordering is chronological (oldest event to latest one).
|
||||||
|
return sortEvents(&(events[i]), &(events[j]))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert all of the events into client events.
|
||||||
|
clientEvents = gomatrixserverlib.ToClientEvents(events, gomatrixserverlib.FormatAll)
|
||||||
|
// Get the position of the first and the last event in the room's topology.
|
||||||
|
// This position is currently determined by the event's depth, so we could
|
||||||
|
// also use it instead of retrieving from the database. However, if we ever
|
||||||
|
// change the way topological positions are defined (as depth isn't the most
|
||||||
|
// reliable way to define it), it would be easier and less troublesome to
|
||||||
|
// only have to change it in one place, i.e. the database.
|
||||||
|
startPos, err := r.db.EventPositionInTopology(
|
||||||
|
r.ctx, events[0].EventID(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
endPos, err := r.db.EventPositionInTopology(
|
||||||
|
r.ctx, events[len(events)-1].EventID(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Generate pagination tokens to send to the client using the positions
|
||||||
|
// retrieved previously.
|
||||||
|
start = types.NewPaginationTokenFromTypeAndPosition(
|
||||||
|
types.PaginationTokenTypeTopology, startPos, 0,
|
||||||
|
)
|
||||||
|
end = types.NewPaginationTokenFromTypeAndPosition(
|
||||||
|
types.PaginationTokenTypeTopology, endPos, 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
if r.backwardOrdering {
|
||||||
|
// A stream/topological position is a cursor located between two events.
|
||||||
|
// While they are identified in the code by the event on their right (if
|
||||||
|
// we consider a left to right chronological order), tokens need to refer
|
||||||
|
// to them by the event on their left, therefore we need to decrement the
|
||||||
|
// end position we send in the response if we're going backward.
|
||||||
|
end.PDUPosition--
|
||||||
|
}
|
||||||
|
|
||||||
|
// The lowest token value is 1, therefore we need to manually set it to that
|
||||||
|
// value if we're below it.
|
||||||
|
if end.PDUPosition < types.StreamPosition(1) {
|
||||||
|
end.PDUPosition = types.StreamPosition(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientEvents, start, end, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleEmptyEventsSlice handles the case where the initial request to the
|
||||||
|
// database returned an empty slice of events. It does so by checking whether
|
||||||
|
// the set is empty because we've reached a backward extremity, and if that is
|
||||||
|
// the case, by retrieving as much events as requested by backfilling from
|
||||||
|
// another homeserver.
|
||||||
|
// Returns an error if there was an issue talking with the database or
|
||||||
|
// backfilling.
|
||||||
|
func (r *messagesReq) handleEmptyEventsSlice() (
|
||||||
|
events []gomatrixserverlib.Event, err error,
|
||||||
|
) {
|
||||||
|
backwardExtremities, err := r.db.BackwardExtremitiesForRoom(r.ctx, r.roomID)
|
||||||
|
|
||||||
|
// Check if we have backward extremities for this room.
|
||||||
|
if len(backwardExtremities) > 0 {
|
||||||
|
// If so, retrieve as much events as needed through backfilling.
|
||||||
|
events, err = r.backfill(backwardExtremities, r.limit)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If not, it means the slice was empty because we reached the room's
|
||||||
|
// creation, so return an empty slice.
|
||||||
|
events = []gomatrixserverlib.Event{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleNonEmptyEventsSlice handles the case where the initial request to the
|
||||||
|
// database returned a non-empty slice of events. It does so by checking whether
|
||||||
|
// events are missing from the expected result, and retrieve missing events
|
||||||
|
// through backfilling if needed.
|
||||||
|
// Returns an error if there was an issue while backfilling.
|
||||||
|
func (r *messagesReq) handleNonEmptyEventsSlice(streamEvents []types.StreamEvent) (
|
||||||
|
events []gomatrixserverlib.Event, err error,
|
||||||
|
) {
|
||||||
|
// Check if we have enough events.
|
||||||
|
isSetLargeEnough := true
|
||||||
|
if len(streamEvents) < r.limit {
|
||||||
|
if r.backwardOrdering {
|
||||||
|
if r.wasToProvided {
|
||||||
|
// The condition in the SQL query is a strict "greater than" so
|
||||||
|
// we need to check against to-1.
|
||||||
|
streamPos := types.StreamPosition(streamEvents[len(streamEvents)-1].StreamPosition)
|
||||||
|
isSetLargeEnough = (r.to.PDUPosition-1 == streamPos)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
streamPos := types.StreamPosition(streamEvents[0].StreamPosition)
|
||||||
|
isSetLargeEnough = (r.from.PDUPosition-1 == streamPos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the slice contains a backward extremity.
|
||||||
|
backwardExtremities, err := r.db.BackwardExtremitiesForRoom(r.ctx, r.roomID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backfill is needed if we've reached a backward extremity and need more
|
||||||
|
// events. It's only needed if the direction is backward.
|
||||||
|
if len(backwardExtremities) > 0 && !isSetLargeEnough && r.backwardOrdering {
|
||||||
|
var pdus []gomatrixserverlib.Event
|
||||||
|
// Only ask the remote server for enough events to reach the limit.
|
||||||
|
pdus, err = r.backfill(backwardExtremities, r.limit-len(streamEvents))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the PDUs to the list to send back to the client.
|
||||||
|
events = append(events, pdus...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the events ve previously retrieved locally.
|
||||||
|
events = append(events, r.db.StreamEventsToEvents(nil, streamEvents)...)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// containsBackwardExtremity checks if a slice of StreamEvent contains a
|
||||||
|
// backward extremity. It does so by selecting the earliest event in the slice
|
||||||
|
// and by checking the presence in the database of all of its parent events, and
|
||||||
|
// considers the event itself a backward extremity if at least one of the parent
|
||||||
|
// events doesn't exist in the database.
|
||||||
|
// Returns an error if there was an issue with talking to the database.
|
||||||
|
//
|
||||||
|
// This function is unused but currently set to nolint for now until we are
|
||||||
|
// absolutely sure that the changes in matrix-org/dendrite#847 are behaving
|
||||||
|
// properly.
|
||||||
|
// nolint:unused
|
||||||
|
func (r *messagesReq) containsBackwardExtremity(events []types.StreamEvent) (bool, error) {
|
||||||
|
// Select the earliest retrieved event.
|
||||||
|
var ev *types.StreamEvent
|
||||||
|
if r.backwardOrdering {
|
||||||
|
ev = &(events[len(events)-1])
|
||||||
|
} else {
|
||||||
|
ev = &(events[0])
|
||||||
|
}
|
||||||
|
// Get the earliest retrieved event's parents.
|
||||||
|
prevIDs := ev.PrevEventIDs()
|
||||||
|
prevs, err := r.db.Events(r.ctx, prevIDs)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
// Check if we have all of the events we requested. If not, it means we've
|
||||||
|
// reached a backward extremity.
|
||||||
|
var eventInDB bool
|
||||||
|
var id string
|
||||||
|
// Iterate over the IDs we used in the request.
|
||||||
|
for _, id = range prevIDs {
|
||||||
|
eventInDB = false
|
||||||
|
// Iterate over the events we got in response.
|
||||||
|
for _, ev := range prevs {
|
||||||
|
if ev.EventID() == id {
|
||||||
|
eventInDB = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// One occurrence of one the event's parents not being present in the
|
||||||
|
// database is enough to say that the event is a backward extremity.
|
||||||
|
if !eventInDB {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// backfill performs a backfill request over the federation on another
|
||||||
|
// homeserver in the room.
|
||||||
|
// See: https://matrix.org/docs/spec/server_server/latest#get-matrix-federation-v1-backfill-roomid
|
||||||
|
// It also stores the PDUs retrieved from the remote homeserver's response to
|
||||||
|
// the database.
|
||||||
|
// Returns with an empty string if the remote homeserver didn't return with any
|
||||||
|
// event, or if there is no remote homeserver to contact.
|
||||||
|
// Returns an error if there was an issue with retrieving the list of servers in
|
||||||
|
// the room or sending the request.
|
||||||
|
func (r *messagesReq) backfill(fromEventIDs []string, limit int) ([]gomatrixserverlib.Event, error) {
|
||||||
|
// Query the list of servers in the room when one of the backward extremities
|
||||||
|
// was sent.
|
||||||
|
var serversResponse api.QueryServersInRoomAtEventResponse
|
||||||
|
serversRequest := api.QueryServersInRoomAtEventRequest{
|
||||||
|
RoomID: r.roomID,
|
||||||
|
EventID: fromEventIDs[0],
|
||||||
|
}
|
||||||
|
if err := r.queryAPI.QueryServersInRoomAtEvent(r.ctx, &serversRequest, &serversResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the first server from the response, except if that server is us.
|
||||||
|
// In that case, use the second one if the roomserver responded with
|
||||||
|
// enough servers. If not, use an empty string to prevent the backfill
|
||||||
|
// from happening as there's no server to direct the request towards.
|
||||||
|
// TODO: Be smarter at selecting the server to direct the request
|
||||||
|
// towards.
|
||||||
|
srvToBackfillFrom := serversResponse.Servers[0]
|
||||||
|
if srvToBackfillFrom == r.cfg.Matrix.ServerName {
|
||||||
|
if len(serversResponse.Servers) > 1 {
|
||||||
|
srvToBackfillFrom = serversResponse.Servers[1]
|
||||||
|
} else {
|
||||||
|
srvToBackfillFrom = gomatrixserverlib.ServerName("")
|
||||||
|
log.Warn("Not enough servers to backfill from")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pdus := make([]gomatrixserverlib.Event, 0)
|
||||||
|
|
||||||
|
// If the roomserver responded with at least one server that isn't us,
|
||||||
|
// send it a request for backfill.
|
||||||
|
if len(srvToBackfillFrom) > 0 {
|
||||||
|
txn, err := r.federation.Backfill(
|
||||||
|
r.ctx, srvToBackfillFrom, r.roomID, limit, fromEventIDs,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pdus = txn.PDUs
|
||||||
|
|
||||||
|
// Store the events in the database, while marking them as unfit to show
|
||||||
|
// up in responses to sync requests.
|
||||||
|
for _, pdu := range pdus {
|
||||||
|
if _, err = r.db.WriteEvent(
|
||||||
|
r.ctx, &pdu, []gomatrixserverlib.Event{}, []string{}, []string{},
|
||||||
|
nil, true,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pdus, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setToDefault returns the default value for the "to" query parameter of a
|
||||||
|
// request to /messages if not provided. It defaults to either the earliest
|
||||||
|
// topological position (if we're going backward) or to the latest one (if we're
|
||||||
|
// going forward).
|
||||||
|
// Returns an error if there was an issue with retrieving the latest position
|
||||||
|
// from the database
|
||||||
|
func setToDefault(
|
||||||
|
ctx context.Context, db storage.Database, backwardOrdering bool,
|
||||||
|
roomID string,
|
||||||
|
) (to *types.PaginationToken, err error) {
|
||||||
|
if backwardOrdering {
|
||||||
|
to = types.NewPaginationTokenFromTypeAndPosition(types.PaginationTokenTypeTopology, 1, 0)
|
||||||
|
} else {
|
||||||
|
var pos types.StreamPosition
|
||||||
|
pos, err = db.MaxTopologicalPosition(ctx, roomID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
to = types.NewPaginationTokenFromTypeAndPosition(types.PaginationTokenTypeTopology, pos, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// sortEvents is a function to give to sort.SliceStable, and compares the
|
||||||
|
// timestamp of two Matrix events.
|
||||||
|
// Returns true if the first event happened before the second one, false
|
||||||
|
// otherwise.
|
||||||
|
func sortEvents(e1 *gomatrixserverlib.Event, e2 *gomatrixserverlib.Event) bool {
|
||||||
|
t := e1.OriginServerTS().Time()
|
||||||
|
return e2.OriginServerTS().Time().After(t)
|
||||||
|
}
|
||||||
|
|
@ -22,8 +22,11 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/dendrite/common/config"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/syncapi/storage"
|
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||||
"github.com/matrix-org/dendrite/syncapi/sync"
|
"github.com/matrix-org/dendrite/syncapi/sync"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -34,7 +37,12 @@ const pathPrefixR0 = "/_matrix/client/r0"
|
||||||
// Due to Setup being used to call many other functions, a gocyclo nolint is
|
// Due to Setup being used to call many other functions, a gocyclo nolint is
|
||||||
// applied:
|
// applied:
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
func Setup(apiMux *mux.Router, srp *sync.RequestPool, syncDB *storage.SyncServerDatasource, deviceDB *devices.Database) {
|
func Setup(
|
||||||
|
apiMux *mux.Router, srp *sync.RequestPool, syncDB storage.Database,
|
||||||
|
deviceDB *devices.Database, federation *gomatrixserverlib.FederationClient,
|
||||||
|
queryAPI api.RoomserverQueryAPI,
|
||||||
|
cfg *config.Dendrite,
|
||||||
|
) {
|
||||||
r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter()
|
r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter()
|
||||||
|
|
||||||
authData := auth.Data{
|
authData := auth.Data{
|
||||||
|
|
@ -71,4 +79,12 @@ func Setup(apiMux *mux.Router, srp *sync.RequestPool, syncDB *storage.SyncServer
|
||||||
}
|
}
|
||||||
return OnIncomingStateTypeRequest(req, syncDB, vars["roomID"], vars["type"], vars["stateKey"])
|
return OnIncomingStateTypeRequest(req, syncDB, vars["roomID"], vars["type"], vars["stateKey"])
|
||||||
})).Methods(http.MethodGet, http.MethodOptions)
|
})).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
r0mux.Handle("/rooms/{roomID}/messages", common.MakeAuthAPI("room_messages", authData, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
|
||||||
|
vars, err := common.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
if err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
return OnIncomingMessagesRequest(req, syncDB, vars["roomID"], federation, queryAPI, cfg)
|
||||||
|
})).Methods(http.MethodGet, http.MethodOptions)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/dendrite/syncapi/storage"
|
"github.com/matrix-org/dendrite/syncapi/storage"
|
||||||
"github.com/matrix-org/dendrite/syncapi/types"
|
"github.com/matrix-org/dendrite/syncapi/types"
|
||||||
|
"github.com/matrix-org/gomatrix"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
@ -40,11 +41,11 @@ type stateEventInStateResp struct {
|
||||||
// TODO: Check if the user is in the room. If not, check if the room's history
|
// TODO: Check if the user is in the room. If not, check if the room's history
|
||||||
// is publicly visible. Current behaviour is returning an empty array if the
|
// is publicly visible. Current behaviour is returning an empty array if the
|
||||||
// user cannot see the room's history.
|
// user cannot see the room's history.
|
||||||
func OnIncomingStateRequest(req *http.Request, db *storage.SyncServerDatasource, roomID string) util.JSONResponse {
|
func OnIncomingStateRequest(req *http.Request, db storage.Database, roomID string) util.JSONResponse {
|
||||||
// TODO(#287): Auth request and handle the case where the user has left (where
|
// TODO(#287): Auth request and handle the case where the user has left (where
|
||||||
// we should return the state at the poin they left)
|
// we should return the state at the poin they left)
|
||||||
|
|
||||||
stateFilterPart := gomatrixserverlib.DefaultFilterPart()
|
stateFilterPart := gomatrix.DefaultFilterPart()
|
||||||
// TODO: stateFilterPart should not limit the number of state events (or only limits abusive number of events)
|
// TODO: stateFilterPart should not limit the number of state events (or only limits abusive number of events)
|
||||||
|
|
||||||
stateEvents, err := db.GetStateEventsForRoom(req.Context(), roomID, &stateFilterPart)
|
stateEvents, err := db.GetStateEventsForRoom(req.Context(), roomID, &stateFilterPart)
|
||||||
|
|
@ -87,7 +88,7 @@ func OnIncomingStateRequest(req *http.Request, db *storage.SyncServerDatasource,
|
||||||
// /rooms/{roomID}/state/{type}/{statekey} request. It will look in current
|
// /rooms/{roomID}/state/{type}/{statekey} request. It will look in current
|
||||||
// state to see if there is an event with that type and state key, if there
|
// state to see if there is an event with that type and state key, if there
|
||||||
// is then (by default) we return the content, otherwise a 404.
|
// is then (by default) we return the content, otherwise a 404.
|
||||||
func OnIncomingStateTypeRequest(req *http.Request, db *storage.SyncServerDatasource, roomID string, evType, stateKey string) util.JSONResponse {
|
func OnIncomingStateTypeRequest(req *http.Request, db storage.Database, roomID string, evType, stateKey string) util.JSONResponse {
|
||||||
// TODO(#287): Auth request and handle the case where the user has left (where
|
// TODO(#287): Auth request and handle the case where the user has left (where
|
||||||
// we should return the state at the poin they left)
|
// we should return the state at the poin they left)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -20,7 +21,8 @@ import (
|
||||||
|
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/dendrite/syncapi/types"
|
||||||
|
"github.com/matrix-org/gomatrix"
|
||||||
)
|
)
|
||||||
|
|
||||||
const accountDataSchema = `
|
const accountDataSchema = `
|
||||||
|
|
@ -88,7 +90,7 @@ func (s *accountDataStatements) prepare(db *sql.DB) (err error) {
|
||||||
func (s *accountDataStatements) insertAccountData(
|
func (s *accountDataStatements) insertAccountData(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userID, roomID, dataType string,
|
userID, roomID, dataType string,
|
||||||
) (pos int64, err error) {
|
) (pos types.StreamPosition, err error) {
|
||||||
err = s.insertAccountDataStmt.QueryRowContext(ctx, userID, roomID, dataType).Scan(&pos)
|
err = s.insertAccountDataStmt.QueryRowContext(ctx, userID, roomID, dataType).Scan(&pos)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -96,8 +98,8 @@ func (s *accountDataStatements) insertAccountData(
|
||||||
func (s *accountDataStatements) selectAccountDataInRange(
|
func (s *accountDataStatements) selectAccountDataInRange(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userID string,
|
userID string,
|
||||||
oldPos, newPos int64,
|
oldPos, newPos types.StreamPosition,
|
||||||
accountDataFilterPart *gomatrixserverlib.FilterPart,
|
accountDataFilterPart *gomatrix.FilterPart,
|
||||||
) (data map[string][]string, err error) {
|
) (data map[string][]string, err error) {
|
||||||
data = make(map[string][]string)
|
data = make(map[string][]string)
|
||||||
|
|
||||||
119
syncapi/storage/postgres/backward_extremities_table.go
Normal file
119
syncapi/storage/postgres/backward_extremities_table.go
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
// Copyright 2018 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
const backwardExtremitiesSchema = `
|
||||||
|
-- Stores output room events received from the roomserver.
|
||||||
|
CREATE TABLE IF NOT EXISTS syncapi_backward_extremities (
|
||||||
|
-- The 'room_id' key for the event.
|
||||||
|
room_id TEXT NOT NULL,
|
||||||
|
-- The event ID for the event.
|
||||||
|
event_id TEXT NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY(room_id, event_id)
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
||||||
|
const insertBackwardExtremitySQL = "" +
|
||||||
|
"INSERT INTO syncapi_backward_extremities (room_id, event_id)" +
|
||||||
|
" VALUES ($1, $2)" +
|
||||||
|
" ON CONFLICT DO NOTHING"
|
||||||
|
|
||||||
|
const selectBackwardExtremitiesForRoomSQL = "" +
|
||||||
|
"SELECT event_id FROM syncapi_backward_extremities WHERE room_id = $1"
|
||||||
|
|
||||||
|
const isBackwardExtremitySQL = "" +
|
||||||
|
"SELECT EXISTS (" +
|
||||||
|
" SELECT TRUE FROM syncapi_backward_extremities" +
|
||||||
|
" WHERE room_id = $1 AND event_id = $2" +
|
||||||
|
")"
|
||||||
|
|
||||||
|
const deleteBackwardExtremitySQL = "" +
|
||||||
|
"DELETE FROM syncapi_backward_extremities WHERE room_id = $1 AND event_id = $2"
|
||||||
|
|
||||||
|
type backwardExtremitiesStatements struct {
|
||||||
|
insertBackwardExtremityStmt *sql.Stmt
|
||||||
|
selectBackwardExtremitiesForRoomStmt *sql.Stmt
|
||||||
|
isBackwardExtremityStmt *sql.Stmt
|
||||||
|
deleteBackwardExtremityStmt *sql.Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *backwardExtremitiesStatements) prepare(db *sql.DB) (err error) {
|
||||||
|
_, err = db.Exec(backwardExtremitiesSchema)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.insertBackwardExtremityStmt, err = db.Prepare(insertBackwardExtremitySQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.selectBackwardExtremitiesForRoomStmt, err = db.Prepare(selectBackwardExtremitiesForRoomSQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.isBackwardExtremityStmt, err = db.Prepare(isBackwardExtremitySQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s.deleteBackwardExtremityStmt, err = db.Prepare(deleteBackwardExtremitySQL); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *backwardExtremitiesStatements) insertsBackwardExtremity(
|
||||||
|
ctx context.Context, roomID, eventID string,
|
||||||
|
) (err error) {
|
||||||
|
_, err = s.insertBackwardExtremityStmt.ExecContext(ctx, roomID, eventID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *backwardExtremitiesStatements) selectBackwardExtremitiesForRoom(
|
||||||
|
ctx context.Context, roomID string,
|
||||||
|
) (eventIDs []string, err error) {
|
||||||
|
eventIDs = make([]string, 0)
|
||||||
|
|
||||||
|
rows, err := s.selectBackwardExtremitiesForRoomStmt.QueryContext(ctx, roomID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var eID string
|
||||||
|
if err = rows.Scan(&eID); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
eventIDs = append(eventIDs, eID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *backwardExtremitiesStatements) isBackwardExtremity(
|
||||||
|
ctx context.Context, roomID, eventID string,
|
||||||
|
) (isBE bool, err error) {
|
||||||
|
err = s.isBackwardExtremityStmt.QueryRowContext(ctx, roomID, eventID).Scan(&isBE)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *backwardExtremitiesStatements) deleteBackwardExtremity(
|
||||||
|
ctx context.Context, roomID, eventID string,
|
||||||
|
) (err error) {
|
||||||
|
_, err = s.insertBackwardExtremityStmt.ExecContext(ctx, roomID, eventID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2017 Vector Creations Ltd
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -21,6 +22,8 @@ import (
|
||||||
|
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/dendrite/syncapi/types"
|
||||||
|
"github.com/matrix-org/gomatrix"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -84,7 +87,12 @@ const selectStateEventSQL = "" +
|
||||||
"SELECT event_json FROM syncapi_current_room_state WHERE room_id = $1 AND type = $2 AND state_key = $3"
|
"SELECT event_json FROM syncapi_current_room_state WHERE room_id = $1 AND type = $2 AND state_key = $3"
|
||||||
|
|
||||||
const selectEventsWithEventIDsSQL = "" +
|
const selectEventsWithEventIDsSQL = "" +
|
||||||
"SELECT added_at, event_json FROM syncapi_current_room_state WHERE event_id = ANY($1)"
|
// TODO: The session_id and transaction_id blanks are here because otherwise
|
||||||
|
// the rowsToStreamEvents expects there to be exactly five columns. We need to
|
||||||
|
// figure out if these really need to be in the DB, and if so, we need a
|
||||||
|
// better permanent fix for this. - neilalexander, 2 Jan 2020
|
||||||
|
"SELECT added_at, event_json, 0 AS session_id, false AS exclude_from_sync, '' AS transaction_id" +
|
||||||
|
" FROM syncapi_current_room_state WHERE event_id = ANY($1)"
|
||||||
|
|
||||||
type currentRoomStateStatements struct {
|
type currentRoomStateStatements struct {
|
||||||
upsertRoomStateStmt *sql.Stmt
|
upsertRoomStateStmt *sql.Stmt
|
||||||
|
|
@ -177,7 +185,7 @@ func (s *currentRoomStateStatements) selectRoomIDsWithMembership(
|
||||||
// CurrentState returns all the current state events for the given room.
|
// CurrentState returns all the current state events for the given room.
|
||||||
func (s *currentRoomStateStatements) selectCurrentState(
|
func (s *currentRoomStateStatements) selectCurrentState(
|
||||||
ctx context.Context, txn *sql.Tx, roomID string,
|
ctx context.Context, txn *sql.Tx, roomID string,
|
||||||
stateFilterPart *gomatrixserverlib.FilterPart,
|
stateFilterPart *gomatrix.FilterPart,
|
||||||
) ([]gomatrixserverlib.Event, error) {
|
) ([]gomatrixserverlib.Event, error) {
|
||||||
stmt := common.TxStmt(txn, s.selectCurrentStateStmt)
|
stmt := common.TxStmt(txn, s.selectCurrentStateStmt)
|
||||||
rows, err := stmt.QueryContext(ctx, roomID,
|
rows, err := stmt.QueryContext(ctx, roomID,
|
||||||
|
|
@ -206,7 +214,7 @@ func (s *currentRoomStateStatements) deleteRoomStateByEventID(
|
||||||
|
|
||||||
func (s *currentRoomStateStatements) upsertRoomState(
|
func (s *currentRoomStateStatements) upsertRoomState(
|
||||||
ctx context.Context, txn *sql.Tx,
|
ctx context.Context, txn *sql.Tx,
|
||||||
event gomatrixserverlib.Event, membership *string, addedAt int64,
|
event gomatrixserverlib.Event, membership *string, addedAt types.StreamPosition,
|
||||||
) error {
|
) error {
|
||||||
// Parse content as JSON and search for an "url" key
|
// Parse content as JSON and search for an "url" key
|
||||||
containsURL := false
|
containsURL := false
|
||||||
|
|
@ -235,7 +243,7 @@ func (s *currentRoomStateStatements) upsertRoomState(
|
||||||
|
|
||||||
func (s *currentRoomStateStatements) selectEventsWithEventIDs(
|
func (s *currentRoomStateStatements) selectEventsWithEventIDs(
|
||||||
ctx context.Context, txn *sql.Tx, eventIDs []string,
|
ctx context.Context, txn *sql.Tx, eventIDs []string,
|
||||||
) ([]streamEvent, error) {
|
) ([]types.StreamEvent, error) {
|
||||||
stmt := common.TxStmt(txn, s.selectEventsWithEventIDsStmt)
|
stmt := common.TxStmt(txn, s.selectEventsWithEventIDsStmt)
|
||||||
rows, err := stmt.QueryContext(ctx, pq.StringArray(eventIDs))
|
rows, err := stmt.QueryContext(ctx, pq.StringArray(eventIDs))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package storage
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -1,10 +1,26 @@
|
||||||
package storage
|
// Copyright 2017-2018 New Vector Ltd
|
||||||
|
// Copyright 2019-2020 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/common"
|
"github.com/matrix-org/dendrite/common"
|
||||||
|
"github.com/matrix-org/dendrite/syncapi/types"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -71,7 +87,7 @@ func (s *inviteEventsStatements) prepare(db *sql.DB) (err error) {
|
||||||
|
|
||||||
func (s *inviteEventsStatements) insertInviteEvent(
|
func (s *inviteEventsStatements) insertInviteEvent(
|
||||||
ctx context.Context, inviteEvent gomatrixserverlib.Event,
|
ctx context.Context, inviteEvent gomatrixserverlib.Event,
|
||||||
) (streamPos int64, err error) {
|
) (streamPos types.StreamPosition, err error) {
|
||||||
err = s.insertInviteEventStmt.QueryRowContext(
|
err = s.insertInviteEventStmt.QueryRowContext(
|
||||||
ctx,
|
ctx,
|
||||||
inviteEvent.RoomID(),
|
inviteEvent.RoomID(),
|
||||||
|
|
@ -92,7 +108,7 @@ func (s *inviteEventsStatements) deleteInviteEvent(
|
||||||
// selectInviteEventsInRange returns a map of room ID to invite event for the
|
// selectInviteEventsInRange returns a map of room ID to invite event for the
|
||||||
// active invites for the target user ID in the supplied range.
|
// active invites for the target user ID in the supplied range.
|
||||||
func (s *inviteEventsStatements) selectInviteEventsInRange(
|
func (s *inviteEventsStatements) selectInviteEventsInRange(
|
||||||
ctx context.Context, txn *sql.Tx, targetUserID string, startPos, endPos int64,
|
ctx context.Context, txn *sql.Tx, targetUserID string, startPos, endPos types.StreamPosition,
|
||||||
) (map[string]gomatrixserverlib.Event, error) {
|
) (map[string]gomatrixserverlib.Event, error) {
|
||||||
stmt := common.TxStmt(txn, s.selectInviteEventsInRangeStmt)
|
stmt := common.TxStmt(txn, s.selectInviteEventsInRangeStmt)
|
||||||
rows, err := stmt.QueryContext(ctx, targetUserID, startPos, endPos)
|
rows, err := stmt.QueryContext(ctx, targetUserID, startPos, endPos)
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue