Commit 92dfc627 authored by Tulir Asokan's avatar Tulir Asokan

Add more features

parent 046b23f2
......@@ -11,7 +11,6 @@ not been implemented.
* Cache stuff on disk
* Reading events as they come down /sync
* Media and account data
* Maybe somewhat follow [MSC2312](https://github.com/matrix-org/matrix-doc/pull/2312) for paths.
* Slashes are bad and URL encoding isn't nice, so for room v3 event IDs, encode/decode them to show up as room v4 event IDs.
* Room v1/v2 are only supported if users don't modify their servers to send event IDs with slashes or null characters.
* Eventually (not soon), support end-to-end encryption.
......
// mautrixfs - A Matrix client as a FUSE filesystem.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package main
import (
"context"
"regexp"
"syscall"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
"maunium.net/go/mautrix"
)
type AliasRoot struct {
fs.Inode
client *mautrix.Client
}
var _ = (fs.NodeGetattrer)((*AliasRoot)(nil))
var _ = (fs.NodeLookuper)((*AliasRoot)(nil))
var ServerNameRegex = regexp.MustCompile("(?:(?:\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})|(?:\\[(?:[0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}])|(?:[\\w-.]{1,255}))(?::\\d{1,5})?")
func (alias *AliasRoot) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
out.Mode = 0555
return OK
}
func (alias *AliasRoot) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
resolved := alias.GetChild(name)
if resolved != nil {
return resolved, OK
} else if !ServerNameRegex.MatchString(name) {
return nil, syscall.ENOENT
}
return alias.NewInode(ctx, &AliasServerRoot{
client: alias.client,
server: name,
}, fs.StableAttr{Mode: syscall.S_IFDIR}), OK
}
// mautrixfs - A Matrix client as a FUSE filesystem.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package main
import (
"context"
"fmt"
"syscall"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
"maunium.net/go/mautrix"
)
type AliasServerRoot struct {
fs.Inode
server string
client *mautrix.Client
}
var _ = (fs.NodeGetattrer)((*AliasServerRoot)(nil))
var _ = (fs.NodeLookuper)((*AliasServerRoot)(nil))
var _ = (fs.NodeUnlinker)((*AliasServerRoot)(nil))
func (aliasServer *AliasServerRoot) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
out.Mode = 0555
return OK
}
func (aliasServer *AliasServerRoot) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
resolved := aliasServer.GetChild(name)
if resolved != nil {
return resolved, OK
}
alias := fmt.Sprintf("#%s:%s", name, aliasServer.server)
fmt.Println("Alias lookup", alias)
data, err := aliasServer.client.ResolveAlias(alias)
if err != nil || data == nil {
return nil, syscall.ENOENT
}
return aliasServer.NewInode(ctx, &fs.MemSymlink{
Attr: fuse.Attr{},
Data: []byte(fmt.Sprintf("../../room/%s", data.RoomID)),
}, fs.StableAttr{Mode: syscall.S_IFLNK}), OK
}
func (aliasServer *AliasServerRoot) Unlink(ctx context.Context, name string) syscall.Errno {
alias := fmt.Sprintf("#%s:%s", name, aliasServer.server)
fmt.Println("Alias unlink", alias)
_, err := aliasServer.client.DeleteAlias(alias)
return httpToErrno(err, true)
}
// mautrixfs - A Matrix client as a FUSE filesystem.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package main
import (
"context"
"syscall"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
"maunium.net/go/mautrix"
)
type RoomEventNode struct {
fs.Inode
room *RoomNode
id string
data []byte
client *mautrix.Client
}
var _ = (fs.NodeGetattrer)((*RoomEventNode)(nil))
var _ = (fs.NodeOpener)((*RoomEventNode)(nil))
var _ = (fs.NodeReader)((*RoomEventNode)(nil))
func (event *RoomEventNode) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
out.Mode = 0444
out.Size = uint64(len(event.data))
return OK
}
func (event *RoomEventNode) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
return nil, fuse.FOPEN_KEEP_CACHE, OK
}
func (event *RoomEventNode) Read(ctx context.Context, fh fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
end := int(off) + len(dest)
if end > len(event.data) {
end = len(event.data)
}
return fuse.ReadResultData(event.data[off:end]), OK
}
......@@ -8,3 +8,7 @@ require (
)
replace gopkg.in/russross/blackfriday.v2 => github.com/russross/blackfriday/v2 v2.0.1
replace maunium.net/go/mautrix => ../../Matrix/mautrix-go
replace github.com/hanwen/go-fuse/v2 => ../../Go/go-fuse
......@@ -19,6 +19,7 @@ package main
import (
"log"
"os"
"syscall"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
......@@ -43,3 +44,29 @@ func main() {
}
server.Wait()
}
func httpToErrno(err error, isDelete bool) syscall.Errno {
if err != nil {
httpErr, ok := err.(mautrix.HTTPError)
if !ok {
switch err {
default:
return syscall.EIO
}
}
switch httpErr.Code {
case 401, 403:
return syscall.EACCES
case 404:
if isDelete {
return OK
} else {
return syscall.ENOENT
}
default:
return syscall.EIO
}
}
return OK
}
// mautrixfs - A Matrix client as a FUSE filesystem.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package main
import (
"context"
"fmt"
"net/http"
"strings"
"syscall"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
"maunium.net/go/mautrix"
)
type RoomEventRoot struct {
fs.Inode
room *RoomNode
client *mautrix.Client
}
var _ = (fs.NodeGetattrer)((*RoomEventRoot)(nil))
var _ = (fs.NodeLookuper)((*RoomEventRoot)(nil))
var _ = (fs.NodeUnlinker)((*RoomEventRoot)(nil))
func (events *RoomEventRoot) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
out.Mode = 0555
return OK
}
func (events *RoomEventRoot) mutateEventID(eventID string) string {
if events.room.Version == "3" {
eventID = strings.ReplaceAll(eventID, "-", "+")
eventID = strings.ReplaceAll(eventID, "_", "/")
}
return eventID
}
func (events *RoomEventRoot) Unlink(ctx context.Context, name string) syscall.Errno {
eventID := events.mutateEventID(name)
fmt.Println("Event unlink", eventID)
_, err := events.client.RedactEvent(events.room.ID, eventID)
return httpToErrno(err, true)
}
func (events *RoomEventRoot) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
eventID := events.mutateEventID(name)
fmt.Println("Event lookup", eventID)
url := events.client.BuildURL("rooms", events.room.ID, "event", eventID)
data, err := events.client.MakeRequest(http.MethodGet, url, nil, nil)
if err != nil || data == nil {
return nil, syscall.ENOENT
}
return events.NewInode(ctx, &RoomEventNode{
room: events.room,
id: eventID,
data: data,
}, fs.StableAttr{Mode: syscall.S_IFREG}), OK
}
// mautrixfs - A Matrix client as a FUSE filesystem.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package main
import (
"context"
"fmt"
"net/http"
"syscall"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
"maunium.net/go/mautrix"
)
type RoomKeyedStateRoot struct {
fs.Inode
room *RoomNode
client *mautrix.Client
}
func (keyedState *RoomKeyedStateRoot) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
out.Mode = 0555
return OK
}
func (keyedState *RoomKeyedStateRoot) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
fmt.Println("Keyed state lookup", name)
return keyedState.NewInode(ctx, &RoomKeyedStateEvent{
room: keyedState.room,
eventType: name,
client: keyedState.client,
}, fs.StableAttr{Mode: syscall.S_IFDIR}), OK
}
type RoomKeyedStateEvent struct {
fs.Inode
room *RoomNode
eventType string
client *mautrix.Client
}
func (keyedStateEvent *RoomKeyedStateEvent) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
fmt.Println("Keyed state lookup", keyedStateEvent.eventType, name)
url := keyedStateEvent.client.BuildURL("rooms", keyedStateEvent.room.ID, "state", keyedStateEvent.eventType, name)
data, err := keyedStateEvent.client.MakeRequest(http.MethodGet, url, nil, nil)
if err != nil || data == nil {
return nil, syscall.ENOENT
}
return keyedStateEvent.NewInode(ctx, &fs.MemRegularFile{
Data: data,
Attr: fuse.Attr{
Mode: 0444,
},
}, fs.StableAttr{ Mode: syscall.S_IFREG }), OK
}
......@@ -42,6 +42,10 @@ func (room *RoomNode) OnAdd(ctx context.Context) {
room.AddChild("version", version, false)
eventsInode := room.NewPersistentInode(ctx, &RoomEventRoot{room: room, client: room.client}, fs.StableAttr{Mode: syscall.S_IFDIR})
room.AddChild("event", eventsInode, false)
stateInode := room.NewPersistentInode(ctx, &RoomStateRoot{room: room, client: room.client}, fs.StableAttr{Mode: syscall.S_IFDIR})
room.AddChild("state", stateInode, false)
keyedStateInode := room.NewPersistentInode(ctx, &RoomKeyedStateRoot{room: room, client: room.client}, fs.StableAttr{Mode: syscall.S_IFDIR})
room.AddChild("keyed_state", keyedStateInode, false)
}
func (room *RoomNode) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
......
......@@ -18,6 +18,7 @@ package main
import (
"context"
"fmt"
"syscall"
"github.com/hanwen/go-fuse/v2/fs"
......@@ -63,3 +64,32 @@ func (r *RoomRoot) Lookup(ctx context.Context, name string, out *fuse.EntryOut)
inode := r.NewInode(ctx, roomNode, fs.StableAttr{Mode: syscall.S_IFDIR})
return inode, OK
}
type RoomListStream struct {
next int
data []string
}
func (rls *RoomListStream) HasNext() bool {
return rls.next < len(rls.data)
}
func (rls *RoomListStream) Next() (fuse.DirEntry, syscall.Errno) {
rls.next += 1
return fuse.DirEntry{
Mode: 0555,
Name: rls.data[rls.next - 1],
}, OK
}
func (rls *RoomListStream) Close() {}
func (r *RoomRoot) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
fmt.Println("Readdir rooms")
resp, err := r.client.JoinedRooms()
if err != nil {
return nil, syscall.EIO
}
return &RoomListStream{ data: resp.JoinedRooms }, OK
}
......@@ -18,8 +18,8 @@ package main
import (
"context"
"fmt"
"net/http"
"strings"
"syscall"
"github.com/hanwen/go-fuse/v2/fs"
......@@ -28,30 +28,27 @@ import (
"maunium.net/go/mautrix"
)
type RoomEventRoot struct {
type RoomStateRoot struct {
fs.Inode
room *RoomNode
client *mautrix.Client
}
func (events *RoomEventRoot) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
func (state *RoomStateRoot) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
out.Mode = 0555
return OK
}
func (events *RoomEventRoot) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
if events.room.Version == "3" {
name = strings.ReplaceAll(name,"-", "+")
name = strings.ReplaceAll(name,"_", "/")
}
url := events.client.BuildURL("rooms", events.room.ID, "event", name)
data, err := events.client.MakeRequest(http.MethodGet, url, nil, nil)
func (state *RoomStateRoot) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
fmt.Println("State lookup", name)
url := state.client.BuildURL("rooms", state.room.ID, "state", name)
data, err := state.client.MakeRequest(http.MethodGet, url, nil, nil)
if err != nil || data == nil {
return nil, syscall.ENOENT
}
return events.NewInode(ctx, &fs.MemRegularFile{
return state.NewInode(ctx, &fs.MemRegularFile{
Data: data,
Attr: fuse.Attr{
Mode: 0444,
......
......@@ -29,8 +29,9 @@ import (
type MatrixRoot struct {
fs.Inode
client *mautrix.Client
rooms *RoomRoot
client *mautrix.Client
rooms *RoomRoot
aliases *AliasRoot
}
func (r *MatrixRoot) OnAdd(ctx context.Context) {
......@@ -39,7 +40,10 @@ func (r *MatrixRoot) OnAdd(ctx context.Context) {
Attr: fuse.Attr{Mode: 0444},
}, fs.StableAttr{Mode: syscall.S_IFREG})
r.AddChild("version", version, false)
r.ForgetPersistent()
r.aliases = &AliasRoot{client: r.client}
r.AddChild("alias", r.NewPersistentInode(ctx, r.aliases, fs.StableAttr{Mode: syscall.S_IFDIR}), false)
r.rooms = &RoomRoot{client: r.client}
r.AddChild("room", r.NewPersistentInode(ctx, r.rooms, fs.StableAttr{Mode: syscall.S_IFDIR}), false)
}
......
// mautrixfs - A Matrix client as a FUSE filesystem.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package main
import (
"context"
"syscall"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
"maunium.net/go/mautrix"
)
type StateEventNode struct {
fs.Inode
room *RoomNode
id string
data []byte
client *mautrix.Client
}
var _ = (fs.NodeGetattrer)((*StateEventNode)(nil))
var _ = (fs.NodeOpener)((*StateEventNode)(nil))
var _ = (fs.NodeReader)((*StateEventNode)(nil))
func (stateEvent *StateEventNode) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
out.Mode = 0444
out.Size = uint64(len(stateEvent.data))
return OK
}
func (stateEvent *StateEventNode) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
return nil, fuse.FOPEN_KEEP_CACHE, OK
}
func (stateEvent *StateEventNode) Read(ctx context.Context, fh fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
end := int(off) + len(dest)
if end > len(stateEvent.data) {
end = len(stateEvent.data)
}
return fuse.ReadResultData(stateEvent.data[off:end]), OK
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment