Display thumbnail for all files and add commands to download and open files
This commit is contained in:
parent
80564b2887
commit
a6f6fb3ef2
@ -66,6 +66,7 @@ type Config struct {
|
|||||||
HistoryPath string `yaml:"history_path"`
|
HistoryPath string `yaml:"history_path"`
|
||||||
RoomListPath string `yaml:"room_list_path"`
|
RoomListPath string `yaml:"room_list_path"`
|
||||||
MediaDir string `yaml:"media_dir"`
|
MediaDir string `yaml:"media_dir"`
|
||||||
|
DownloadDir string `yaml:"download_dir"`
|
||||||
StateDir string `yaml:"state_dir"`
|
StateDir string `yaml:"state_dir"`
|
||||||
|
|
||||||
Preferences UserPreferences `yaml:"-"`
|
Preferences UserPreferences `yaml:"-"`
|
||||||
@ -78,6 +79,7 @@ type Config struct {
|
|||||||
|
|
||||||
// NewConfig creates a config that loads data from the given directory.
|
// NewConfig creates a config that loads data from the given directory.
|
||||||
func NewConfig(configDir, cacheDir string) *Config {
|
func NewConfig(configDir, cacheDir string) *Config {
|
||||||
|
home, _ := os.UserHomeDir()
|
||||||
return &Config{
|
return &Config{
|
||||||
Dir: configDir,
|
Dir: configDir,
|
||||||
CacheDir: cacheDir,
|
CacheDir: cacheDir,
|
||||||
@ -85,6 +87,7 @@ func NewConfig(configDir, cacheDir string) *Config {
|
|||||||
RoomListPath: filepath.Join(cacheDir, "rooms.gob.gz"),
|
RoomListPath: filepath.Join(cacheDir, "rooms.gob.gz"),
|
||||||
StateDir: filepath.Join(cacheDir, "state"),
|
StateDir: filepath.Join(cacheDir, "state"),
|
||||||
MediaDir: filepath.Join(cacheDir, "media"),
|
MediaDir: filepath.Join(cacheDir, "media"),
|
||||||
|
DownloadDir: home,
|
||||||
|
|
||||||
RoomCacheSize: 32,
|
RoomCacheSize: 32,
|
||||||
RoomCacheAge: 1 * 60,
|
RoomCacheAge: 1 * 60,
|
||||||
|
2
go.mod
2
go.mod
@ -21,7 +21,7 @@ require (
|
|||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
|
||||||
gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2
|
gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2
|
||||||
gopkg.in/yaml.v2 v2.2.8
|
gopkg.in/yaml.v2 v2.2.8
|
||||||
maunium.net/go/mautrix v0.1.0-beta.2.0.20200403220206-c81fdd9fe68f
|
maunium.net/go/mautrix v0.1.0-beta.2.0.20200408115320-100bcaabf80b
|
||||||
maunium.net/go/mauview v0.1.0-beta.1
|
maunium.net/go/mauview v0.1.0-beta.1
|
||||||
maunium.net/go/tcell v0.1.0
|
maunium.net/go/tcell v0.1.0
|
||||||
)
|
)
|
||||||
|
2
go.sum
2
go.sum
@ -79,6 +79,8 @@ maunium.net/go/mautrix v0.1.0-beta.1.0.20200320135656-32dd5f172592 h1:WybA4BBUyo
|
|||||||
maunium.net/go/mautrix v0.1.0-beta.1.0.20200320135656-32dd5f172592/go.mod h1:YFMU9DBeXH7cqx7sJLg0DkVxwNPbih8QbpUTYf/IjMM=
|
maunium.net/go/mautrix v0.1.0-beta.1.0.20200320135656-32dd5f172592/go.mod h1:YFMU9DBeXH7cqx7sJLg0DkVxwNPbih8QbpUTYf/IjMM=
|
||||||
maunium.net/go/mautrix v0.1.0-beta.2.0.20200403220206-c81fdd9fe68f h1:Fg7t2i+AtUX4NDN5Ue9HsHO/Vog0JN9CZsOH3u+hDOc=
|
maunium.net/go/mautrix v0.1.0-beta.2.0.20200403220206-c81fdd9fe68f h1:Fg7t2i+AtUX4NDN5Ue9HsHO/Vog0JN9CZsOH3u+hDOc=
|
||||||
maunium.net/go/mautrix v0.1.0-beta.2.0.20200403220206-c81fdd9fe68f/go.mod h1:YFMU9DBeXH7cqx7sJLg0DkVxwNPbih8QbpUTYf/IjMM=
|
maunium.net/go/mautrix v0.1.0-beta.2.0.20200403220206-c81fdd9fe68f/go.mod h1:YFMU9DBeXH7cqx7sJLg0DkVxwNPbih8QbpUTYf/IjMM=
|
||||||
|
maunium.net/go/mautrix v0.1.0-beta.2.0.20200408115320-100bcaabf80b h1:ifTLB2qPUcZmeCeh2/CvlBZz/ddrdXcszdH7H9qFHzE=
|
||||||
|
maunium.net/go/mautrix v0.1.0-beta.2.0.20200408115320-100bcaabf80b/go.mod h1:YFMU9DBeXH7cqx7sJLg0DkVxwNPbih8QbpUTYf/IjMM=
|
||||||
maunium.net/go/mauview v0.1.0-beta.1 h1:hRprD6NTi5Mw7i97DKmgs/TzFQeNpGPytPoswNlU/Ww=
|
maunium.net/go/mauview v0.1.0-beta.1 h1:hRprD6NTi5Mw7i97DKmgs/TzFQeNpGPytPoswNlU/Ww=
|
||||||
maunium.net/go/mauview v0.1.0-beta.1/go.mod h1:og9WbzmWe9SNYNyOFlCv8qa9zMcOvG2nzRJ5vYyud9U=
|
maunium.net/go/mauview v0.1.0-beta.1/go.mod h1:og9WbzmWe9SNYNyOFlCv8qa9zMcOvG2nzRJ5vYyud9U=
|
||||||
maunium.net/go/tcell v0.1.0 h1:XzsEoGCvOw5nac+tlkSLzQcliLYTN4PrtA7ar2ptjSM=
|
maunium.net/go/tcell v0.1.0 h1:XzsEoGCvOw5nac+tlkSLzQcliLYTN4PrtA7ar2ptjSM=
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package ifc
|
package ifc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"maunium.net/go/gomuks/config"
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
"maunium.net/go/gomuks/matrix/event"
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
|
|
||||||
@ -24,12 +25,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Relation struct {
|
type Relation struct {
|
||||||
Type mautrix.RelationType
|
Type mautrix.RelationType
|
||||||
Event *event.Event
|
Event *event.Event
|
||||||
}
|
}
|
||||||
|
|
||||||
type MatrixContainer interface {
|
type MatrixContainer interface {
|
||||||
Client() *mautrix.Client
|
Client() *mautrix.Client
|
||||||
|
Preferences() *config.UserPreferences
|
||||||
InitClient() error
|
InitClient() error
|
||||||
Initialized() bool
|
Initialized() bool
|
||||||
|
|
||||||
@ -55,7 +57,8 @@ type MatrixContainer interface {
|
|||||||
GetRoom(roomID string) *rooms.Room
|
GetRoom(roomID string) *rooms.Room
|
||||||
GetOrCreateRoom(roomID string) *rooms.Room
|
GetOrCreateRoom(roomID string) *rooms.Room
|
||||||
|
|
||||||
Download(mxcURL string) ([]byte, string, string, error)
|
Download(uri mautrix.ContentURI) ([]byte, error)
|
||||||
GetDownloadURL(homeserver, fileID string) string
|
DownloadToDisk(uri mautrix.ContentURI, target string) (string, error)
|
||||||
GetCachePath(homeserver, fileID string) string
|
GetDownloadURL(uri mautrix.ContentURI) string
|
||||||
|
GetCachePath(uri mautrix.ContentURI) string
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
dbg "runtime/debug"
|
dbg "runtime/debug"
|
||||||
"time"
|
"time"
|
||||||
@ -389,6 +388,10 @@ func (c *Container) HandlePreferences(source EventSource, evt *mautrix.Event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Container) Preferences() *config.UserPreferences {
|
||||||
|
return &c.config.Preferences
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Container) SendPreferencesToMatrix() {
|
func (c *Container) SendPreferencesToMatrix() {
|
||||||
defer debug.Recover()
|
defer debug.Recover()
|
||||||
debug.Print("Sending updated preferences:", c.config.Preferences)
|
debug.Print("Sending updated preferences:", c.config.Preferences)
|
||||||
@ -926,22 +929,73 @@ func (c *Container) GetRoom(roomID string) *rooms.Room {
|
|||||||
return c.config.Rooms.Get(roomID)
|
return c.config.Rooms.Get(roomID)
|
||||||
}
|
}
|
||||||
|
|
||||||
var mxcRegex = regexp.MustCompile("mxc://(.+)/(.+)")
|
func cp(src, dst string) error {
|
||||||
|
in, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
out, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(out, in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return out.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) DownloadToDisk(uri mautrix.ContentURI, target string) (fullPath string, err error) {
|
||||||
|
cachePath := c.GetCachePath(uri)
|
||||||
|
if target == "" {
|
||||||
|
fullPath = cachePath
|
||||||
|
} else if !path.IsAbs(target) {
|
||||||
|
fullPath = path.Join(c.config.DownloadDir, target)
|
||||||
|
} else {
|
||||||
|
fullPath = target
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, statErr := os.Stat(cachePath); os.IsNotExist(statErr) {
|
||||||
|
var file *os.File
|
||||||
|
file, err = os.OpenFile(cachePath, os.O_CREATE|os.O_WRONLY, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var resp *http.Response
|
||||||
|
resp, err = c.client.Client.Get(c.GetDownloadURL(uri))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(file, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fullPath != cachePath {
|
||||||
|
err = os.MkdirAll(path.Dir(fullPath), 0700)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = cp(cachePath, fullPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Download fetches the given Matrix content (mxc) URL and returns the data, homeserver, file ID and potential errors.
|
// Download fetches the given Matrix content (mxc) URL and returns the data, homeserver, file ID and potential errors.
|
||||||
//
|
//
|
||||||
// The file will be either read from the media cache (if found) or downloaded from the server.
|
// The file will be either read from the media cache (if found) or downloaded from the server.
|
||||||
func (c *Container) Download(mxcURL string) (data []byte, hs, id string, err error) {
|
func (c *Container) Download(uri mautrix.ContentURI) (data []byte, err error) {
|
||||||
parts := mxcRegex.FindStringSubmatch(mxcURL)
|
cacheFile := c.GetCachePath(uri)
|
||||||
if parts == nil || len(parts) != 3 {
|
|
||||||
err = fmt.Errorf("invalid matrix content URL")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
hs = parts[1]
|
|
||||||
id = parts[2]
|
|
||||||
|
|
||||||
cacheFile := c.GetCachePath(hs, id)
|
|
||||||
var info os.FileInfo
|
var info os.FileInfo
|
||||||
if info, err = os.Stat(cacheFile); err == nil && !info.IsDir() {
|
if info, err = os.Stat(cacheFile); err == nil && !info.IsDir() {
|
||||||
data, err = ioutil.ReadFile(cacheFile)
|
data, err = ioutil.ReadFile(cacheFile)
|
||||||
@ -950,22 +1004,22 @@ func (c *Container) Download(mxcURL string) (data []byte, hs, id string, err err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err = c.download(hs, id, cacheFile)
|
data, err = c.download(uri, cacheFile)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) GetDownloadURL(hs, id string) string {
|
func (c *Container) GetDownloadURL(uri mautrix.ContentURI) string {
|
||||||
dlURL, _ := url.Parse(c.client.HomeserverURL.String())
|
dlURL, _ := url.Parse(c.client.HomeserverURL.String())
|
||||||
if dlURL.Scheme == "" {
|
if dlURL.Scheme == "" {
|
||||||
dlURL.Scheme = "https"
|
dlURL.Scheme = "https"
|
||||||
}
|
}
|
||||||
dlURL.Path = path.Join(dlURL.Path, "/_matrix/media/v1/download", hs, id)
|
dlURL.Path = path.Join(dlURL.Path, "/_matrix/media/v1/download", uri.Homeserver, uri.FileID)
|
||||||
return dlURL.String()
|
return dlURL.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) download(hs, id, cacheFile string) (data []byte, err error) {
|
func (c *Container) download(uri mautrix.ContentURI, cacheFile string) (data []byte, err error) {
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
resp, err = c.client.Client.Get(c.GetDownloadURL(hs, id))
|
resp, err = c.client.Client.Get(c.GetDownloadURL(uri))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -985,13 +1039,13 @@ func (c *Container) download(hs, id, cacheFile string) (data []byte, err error)
|
|||||||
|
|
||||||
// GetCachePath gets the path to the cached version of the given homeserver:fileID combination.
|
// GetCachePath gets the path to the cached version of the given homeserver:fileID combination.
|
||||||
// The file may or may not exist, use Download() to ensure it has been cached.
|
// The file may or may not exist, use Download() to ensure it has been cached.
|
||||||
func (c *Container) GetCachePath(homeserver, fileID string) string {
|
func (c *Container) GetCachePath(uri mautrix.ContentURI) string {
|
||||||
dir := filepath.Join(c.config.MediaDir, homeserver)
|
dir := filepath.Join(c.config.MediaDir, uri.Homeserver)
|
||||||
|
|
||||||
err := os.MkdirAll(dir, 0700)
|
err := os.MkdirAll(dir, 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return filepath.Join(dir, fileID)
|
return filepath.Join(dir, uri.FileID)
|
||||||
}
|
}
|
||||||
|
@ -92,6 +92,8 @@ func NewCommandProcessor(parent *MainView) *CommandProcessor {
|
|||||||
"remove": {"redact"},
|
"remove": {"redact"},
|
||||||
"rm": {"redact"},
|
"rm": {"redact"},
|
||||||
"del": {"redact"},
|
"del": {"redact"},
|
||||||
|
"dl": {"download"},
|
||||||
|
"o": {"open"},
|
||||||
},
|
},
|
||||||
commands: map[string]CommandHandler{
|
commands: map[string]CommandHandler{
|
||||||
"unknown-command": cmdUnknownCommand,
|
"unknown-command": cmdUnknownCommand,
|
||||||
@ -115,6 +117,8 @@ func NewCommandProcessor(parent *MainView) *CommandProcessor {
|
|||||||
"reply": cmdReply,
|
"reply": cmdReply,
|
||||||
"redact": cmdRedact,
|
"redact": cmdRedact,
|
||||||
"react": cmdReact,
|
"react": cmdReact,
|
||||||
|
"download": cmdDownload,
|
||||||
|
"open": cmdOpen,
|
||||||
"sendevent": cmdSendEvent,
|
"sendevent": cmdSendEvent,
|
||||||
"msendevent": cmdMSendEvent,
|
"msendevent": cmdMSendEvent,
|
||||||
"setstate": cmdSetState,
|
"setstate": cmdSetState,
|
||||||
|
@ -150,9 +150,11 @@ func cmdID(cmd *Command) {
|
|||||||
type SelectReason string
|
type SelectReason string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SelectReply SelectReason = "reply to"
|
SelectReply SelectReason = "reply to"
|
||||||
SelectReact = "react to"
|
SelectReact = "react to"
|
||||||
SelectRedact = "redact"
|
SelectRedact = "redact"
|
||||||
|
SelectDownload = "download"
|
||||||
|
SelectOpen = "open"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cmdReply(cmd *Command) {
|
func cmdReply(cmd *Command) {
|
||||||
@ -163,6 +165,14 @@ func cmdRedact(cmd *Command) {
|
|||||||
cmd.Room.StartSelecting(SelectRedact, strings.Join(cmd.Args, " "))
|
cmd.Room.StartSelecting(SelectRedact, strings.Join(cmd.Args, " "))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cmdDownload(cmd *Command) {
|
||||||
|
cmd.Room.StartSelecting(SelectDownload, strings.Join(cmd.Args, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdOpen(cmd *Command) {
|
||||||
|
cmd.Room.StartSelecting(SelectOpen, strings.Join(cmd.Args, " "))
|
||||||
|
}
|
||||||
|
|
||||||
func cmdReact(cmd *Command) {
|
func cmdReact(cmd *Command) {
|
||||||
if len(cmd.Args) == 0 {
|
if len(cmd.Args) == 0 {
|
||||||
cmd.Reply("Usage: /react <reaction>")
|
cmd.Reply("Usage: /react <reaction>")
|
||||||
|
@ -349,8 +349,8 @@ func (view *MessageView) SetSelected(message *messages.UIMessage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (view *MessageView) handleMessageClick(message *messages.UIMessage, mod tcell.ModMask) bool {
|
func (view *MessageView) handleMessageClick(message *messages.UIMessage, mod tcell.ModMask) bool {
|
||||||
if msg, ok := message.Renderer.(*messages.FileMessage); ok && mod > 0 {
|
if msg, ok := message.Renderer.(*messages.FileMessage); ok && mod > 0 && !msg.Thumbnail.IsEmpty() {
|
||||||
open.Open(msg.Path())
|
open.Open(msg.ThumbnailPath())
|
||||||
// No need to re-render
|
// No need to re-render
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,6 @@ import (
|
|||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/interface"
|
|
||||||
"maunium.net/go/gomuks/ui/widget"
|
"maunium.net/go/gomuks/ui/widget"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,7 +35,6 @@ type MessageRenderer interface {
|
|||||||
NotificationContent() string
|
NotificationContent() string
|
||||||
PlainText() string
|
PlainText() string
|
||||||
CalculateBuffer(prefs config.UserPreferences, width int, msg *UIMessage)
|
CalculateBuffer(prefs config.UserPreferences, width int, msg *UIMessage)
|
||||||
RegisterMatrix(matrix ifc.MatrixContainer, prefs config.UserPreferences)
|
|
||||||
Height() int
|
Height() int
|
||||||
Clone() MessageRenderer
|
Clone() MessageRenderer
|
||||||
String() string
|
String() string
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
ifc "maunium.net/go/gomuks/interface"
|
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
"maunium.net/go/gomuks/matrix/event"
|
||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
@ -88,5 +87,3 @@ func (msg *ExpandedTextMessage) Draw(screen mauview.Screen) {
|
|||||||
line.Draw(screen, 0, y)
|
line.Draw(screen, 0, y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *ExpandedTextMessage) RegisterMatrix(matrix ifc.MatrixContainer, prefs config.UserPreferences) {}
|
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
"maunium.net/go/gomuks/matrix/event"
|
||||||
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
@ -34,72 +35,86 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FileMessage struct {
|
type FileMessage struct {
|
||||||
Body string
|
Type mautrix.MessageType
|
||||||
Homeserver string
|
Body string
|
||||||
FileID string
|
URL mautrix.ContentURI
|
||||||
data []byte
|
Thumbnail mautrix.ContentURI
|
||||||
buffer []tstring.TString
|
imageData []byte
|
||||||
|
buffer []tstring.TString
|
||||||
|
|
||||||
matrix ifc.MatrixContainer
|
matrix ifc.MatrixContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFileMessage creates a new FileMessage object with the provided values and the default state.
|
// NewFileMessage creates a new FileMessage object with the provided values and the default state.
|
||||||
func NewFileMessage(matrix ifc.MatrixContainer, evt *event.Event, displayname string, body, homeserver, fileID string, data []byte) *UIMessage {
|
func NewFileMessage(matrix ifc.MatrixContainer, evt *event.Event, displayname string) *UIMessage {
|
||||||
|
url, _ := mautrix.ParseContentURI(evt.Content.URL)
|
||||||
|
thumbnail, _ := mautrix.ParseContentURI(evt.Content.GetInfo().ThumbnailURL)
|
||||||
return newUIMessage(evt, displayname, &FileMessage{
|
return newUIMessage(evt, displayname, &FileMessage{
|
||||||
Body: body,
|
Type: evt.Content.MsgType,
|
||||||
Homeserver: homeserver,
|
Body: evt.Content.Body,
|
||||||
FileID: fileID,
|
URL: url,
|
||||||
data: data,
|
Thumbnail: thumbnail,
|
||||||
matrix: matrix,
|
matrix: matrix,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *FileMessage) Clone() MessageRenderer {
|
func (msg *FileMessage) Clone() MessageRenderer {
|
||||||
data := make([]byte, len(msg.data))
|
data := make([]byte, len(msg.imageData))
|
||||||
copy(data, msg.data)
|
copy(data, msg.imageData)
|
||||||
return &FileMessage{
|
return &FileMessage{
|
||||||
Body: msg.Body,
|
Body: msg.Body,
|
||||||
Homeserver: msg.Homeserver,
|
URL: msg.URL,
|
||||||
FileID: msg.FileID,
|
Thumbnail: msg.Thumbnail,
|
||||||
data: data,
|
imageData: data,
|
||||||
matrix: msg.matrix,
|
matrix: msg.matrix,
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (msg *FileMessage) RegisterMatrix(matrix ifc.MatrixContainer, prefs config.UserPreferences) {
|
|
||||||
msg.matrix = matrix
|
|
||||||
|
|
||||||
if len(msg.data) == 0 && !prefs.DisableDownloads {
|
|
||||||
go msg.updateData()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *FileMessage) NotificationContent() string {
|
func (msg *FileMessage) NotificationContent() string {
|
||||||
return "Sent a file"
|
switch msg.Type {
|
||||||
|
case mautrix.MsgImage:
|
||||||
|
return "Sent an image"
|
||||||
|
case mautrix.MsgAudio:
|
||||||
|
return "Sent an audio file"
|
||||||
|
case mautrix.MsgVideo:
|
||||||
|
return "Sent a video"
|
||||||
|
case mautrix.MsgFile:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return "Sent a file"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *FileMessage) PlainText() string {
|
func (msg *FileMessage) PlainText() string {
|
||||||
return fmt.Sprintf("%s: %s", msg.Body, msg.matrix.GetDownloadURL(msg.Homeserver, msg.FileID))
|
return fmt.Sprintf("%s: %s", msg.Body, msg.matrix.GetDownloadURL(msg.URL))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *FileMessage) String() string {
|
func (msg *FileMessage) String() string {
|
||||||
return fmt.Sprintf(`&messages.FileMessage{Body="%s", Homeserver="%s", FileID="%s"}`, msg.Body, msg.Homeserver, msg.FileID)
|
return fmt.Sprintf(`&messages.FileMessage{Body="%s", URL="%s", Thumbnail="%s"}`, msg.Body, msg.URL, msg.Thumbnail)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *FileMessage) updateData() {
|
func (msg *FileMessage) DownloadPreview() {
|
||||||
defer debug.Recover()
|
url := msg.Thumbnail
|
||||||
debug.Print("Loading file:", msg.Homeserver, msg.FileID)
|
if url.IsEmpty() {
|
||||||
data, _, _, err := msg.matrix.Download(fmt.Sprintf("mxc://%s/%s", msg.Homeserver, msg.FileID))
|
if msg.Type == mautrix.MsgImage && !msg.URL.IsEmpty() {
|
||||||
|
msg.Thumbnail = msg.URL
|
||||||
|
url = msg.Thumbnail
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug.Print("Loading file:", url)
|
||||||
|
data, err := msg.matrix.Download(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Printf("Failed to download file %s/%s: %v", msg.Homeserver, msg.FileID, err)
|
debug.Printf("Failed to download file %s: %v", url, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
debug.Print("File", msg.Homeserver, msg.FileID, "loaded.")
|
debug.Print("File", url, "loaded.")
|
||||||
msg.data = data
|
msg.imageData = data
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *FileMessage) Path() string {
|
func (msg *FileMessage) ThumbnailPath() string {
|
||||||
return msg.matrix.GetCachePath(msg.Homeserver, msg.FileID)
|
return msg.matrix.GetCachePath(msg.Thumbnail)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *FileMessage) CalculateBuffer(prefs config.UserPreferences, width int, uiMsg *UIMessage) {
|
func (msg *FileMessage) CalculateBuffer(prefs config.UserPreferences, width int, uiMsg *UIMessage) {
|
||||||
@ -107,12 +122,12 @@ func (msg *FileMessage) CalculateBuffer(prefs config.UserPreferences, width int,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if prefs.BareMessageView || prefs.DisableImages || uiMsg.Type != "m.image" {
|
if prefs.BareMessageView || prefs.DisableImages || len(msg.imageData) == 0 {
|
||||||
msg.buffer = calculateBufferWithText(prefs, tstring.NewTString(msg.PlainText()), width, uiMsg)
|
msg.buffer = calculateBufferWithText(prefs, tstring.NewTString(msg.PlainText()), width, uiMsg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
img, _, err := image.DecodeConfig(bytes.NewReader(msg.data))
|
img, _, err := image.DecodeConfig(bytes.NewReader(msg.imageData))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Print("File could not be decoded:", err)
|
debug.Print("File could not be decoded:", err)
|
||||||
}
|
}
|
||||||
@ -121,7 +136,7 @@ func (msg *FileMessage) CalculateBuffer(prefs config.UserPreferences, width int,
|
|||||||
imgWidth = width / 3
|
imgWidth = width / 3
|
||||||
}
|
}
|
||||||
|
|
||||||
ansFile, err := ansimage.NewScaledFromReader(bytes.NewReader(msg.data), 0, imgWidth, color.Black)
|
ansFile, err := ansimage.NewScaledFromReader(bytes.NewReader(msg.imageData), 0, imgWidth, color.Black)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg.buffer = []tstring.TString{tstring.NewColorTString("Failed to display image", tcell.ColorRed)}
|
msg.buffer = []tstring.TString{tstring.NewColorTString("Failed to display image", tcell.ColorRed)}
|
||||||
debug.Print("Failed to display image:", err)
|
debug.Print("Failed to display image:", err)
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package messages
|
package messages
|
||||||
|
|
||||||
import (
|
import (
|
||||||
ifc "maunium.net/go/gomuks/interface"
|
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
"maunium.net/go/gomuks/matrix/event"
|
||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
@ -39,8 +38,6 @@ func NewHTMLMessage(evt *event.Event, displayname string, root html.Entity) *UIM
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hw *HTMLMessage) RegisterMatrix(matrix ifc.MatrixContainer, prefs config.UserPreferences) {}
|
|
||||||
|
|
||||||
func (hw *HTMLMessage) Clone() MessageRenderer {
|
func (hw *HTMLMessage) Clone() MessageRenderer {
|
||||||
return &HTMLMessage{
|
return &HTMLMessage{
|
||||||
Root: hw.Root.Clone(),
|
Root: hw.Root.Clone(),
|
||||||
|
@ -24,7 +24,6 @@ import (
|
|||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/debug"
|
|
||||||
"maunium.net/go/gomuks/interface"
|
"maunium.net/go/gomuks/interface"
|
||||||
"maunium.net/go/gomuks/matrix/rooms"
|
"maunium.net/go/gomuks/matrix/rooms"
|
||||||
"maunium.net/go/gomuks/ui/messages/html"
|
"maunium.net/go/gomuks/ui/messages/html"
|
||||||
@ -132,18 +131,19 @@ func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *event.Event
|
|||||||
evt.Content = *evt.Gomuks.Edits[len(evt.Gomuks.Edits)-1].Content.NewContent
|
evt.Content = *evt.Gomuks.Edits[len(evt.Gomuks.Edits)-1].Content.NewContent
|
||||||
}
|
}
|
||||||
switch evt.Content.MsgType {
|
switch evt.Content.MsgType {
|
||||||
case "m.text", "m.notice", "m.emote":
|
case mautrix.MsgText, mautrix.MsgNotice, mautrix.MsgEmote:
|
||||||
if evt.Content.Format == mautrix.FormatHTML {
|
if evt.Content.Format == mautrix.FormatHTML {
|
||||||
return NewHTMLMessage(evt, displayname, html.Parse(room, evt, displayname))
|
return NewHTMLMessage(evt, displayname, html.Parse(room, evt, displayname))
|
||||||
}
|
}
|
||||||
evt.Content.Body = strings.Replace(evt.Content.Body, "\t", " ", -1)
|
evt.Content.Body = strings.Replace(evt.Content.Body, "\t", " ", -1)
|
||||||
return NewTextMessage(evt, displayname, evt.Content.Body)
|
return NewTextMessage(evt, displayname, evt.Content.Body)
|
||||||
case "m.file", "m.video", "m.audio", "m.image":
|
case mautrix.MsgImage, mautrix.MsgVideo, mautrix.MsgAudio, mautrix.MsgFile:
|
||||||
data, hs, id, err := matrix.Download(evt.Content.URL)
|
msg := NewFileMessage(matrix, evt, displayname)
|
||||||
if err != nil {
|
if !matrix.Preferences().DisableDownloads {
|
||||||
debug.Printf("Failed to download %s: %v", evt.Content.URL, err)
|
renderer := msg.Renderer.(*FileMessage)
|
||||||
|
renderer.DownloadPreview()
|
||||||
}
|
}
|
||||||
return NewFileMessage(matrix, evt, displayname, evt.Content.Body, hs, id, data)
|
return msg
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package messages
|
package messages
|
||||||
|
|
||||||
import (
|
import (
|
||||||
ifc "maunium.net/go/gomuks/interface"
|
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
"maunium.net/go/gomuks/matrix/event"
|
||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
"maunium.net/go/tcell"
|
"maunium.net/go/tcell"
|
||||||
@ -64,5 +63,3 @@ func (msg *RedactedMessage) Draw(screen mauview.Screen) {
|
|||||||
screen.SetContent(x, 0, RedactionChar, nil, RedactionStyle)
|
screen.SetContent(x, 0, RedactionChar, nil, RedactionStyle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *RedactedMessage) RegisterMatrix(matrix ifc.MatrixContainer, prefs config.UserPreferences) {}
|
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
ifc "maunium.net/go/gomuks/interface"
|
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
"maunium.net/go/gomuks/matrix/event"
|
||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
|
|
||||||
@ -101,5 +100,3 @@ func (msg *TextMessage) Draw(screen mauview.Screen) {
|
|||||||
line.Draw(screen, 0, y)
|
line.Draw(screen, 0, y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *TextMessage) RegisterMatrix(matrix ifc.MatrixContainer, prefs config.UserPreferences) {}
|
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
|
|
||||||
"maunium.net/go/gomuks/debug"
|
"maunium.net/go/gomuks/debug"
|
||||||
|
"maunium.net/go/gomuks/lib/open"
|
||||||
"maunium.net/go/gomuks/matrix/event"
|
"maunium.net/go/gomuks/matrix/event"
|
||||||
|
|
||||||
"maunium.net/go/mauview"
|
"maunium.net/go/mauview"
|
||||||
@ -191,6 +192,17 @@ func (view *RoomView) OnSelect(message *messages.UIMessage) {
|
|||||||
go view.SendReaction(message.EventID, view.selectContent)
|
go view.SendReaction(message.EventID, view.selectContent)
|
||||||
case SelectRedact:
|
case SelectRedact:
|
||||||
go view.Redact(message.EventID, view.selectContent)
|
go view.Redact(message.EventID, view.selectContent)
|
||||||
|
case SelectDownload, SelectOpen:
|
||||||
|
msg, ok := message.Renderer.(*messages.FileMessage)
|
||||||
|
if ok {
|
||||||
|
path := ""
|
||||||
|
if len(view.selectContent) > 0 {
|
||||||
|
path = view.selectContent
|
||||||
|
} else if view.selectReason == SelectDownload {
|
||||||
|
path = msg.Body
|
||||||
|
}
|
||||||
|
go view.Download(msg.URL, path, view.selectReason == SelectOpen)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
view.selecting = false
|
view.selecting = false
|
||||||
view.selectContent = ""
|
view.selectContent = ""
|
||||||
@ -465,9 +477,22 @@ func (view *RoomView) SetEditing(evt *event.Event) {
|
|||||||
view.input.SetCursorOffset(-1)
|
view.input.SetCursorOffset(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *RoomView) findMessage(current *event.Event, ownMessage, forward bool) *messages.UIMessage {
|
type findFilter func(evt *event.Event) bool
|
||||||
|
|
||||||
|
func (view *RoomView) filterOwnOnly(evt *event.Event) bool {
|
||||||
|
return evt.Sender == view.parent.matrix.Client().UserID && evt.Type == mautrix.EventMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (view *RoomView) filterMediaOnly(evt *event.Event) bool {
|
||||||
|
return evt.Type == mautrix.EventMessage && (
|
||||||
|
evt.Content.MsgType == mautrix.MsgFile ||
|
||||||
|
evt.Content.MsgType == mautrix.MsgImage ||
|
||||||
|
evt.Content.MsgType == mautrix.MsgAudio ||
|
||||||
|
evt.Content.MsgType == mautrix.MsgVideo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (view *RoomView) findMessage(current *event.Event, forward bool, allow findFilter) *messages.UIMessage {
|
||||||
currentFound := current == nil
|
currentFound := current == nil
|
||||||
self := view.parent.matrix.Client().UserID
|
|
||||||
msgs := view.MessageView().messages
|
msgs := view.MessageView().messages
|
||||||
for i := 0; i < len(msgs); i++ {
|
for i := 0; i < len(msgs); i++ {
|
||||||
index := i
|
index := i
|
||||||
@ -478,7 +503,7 @@ func (view *RoomView) findMessage(current *event.Event, ownMessage, forward bool
|
|||||||
if evt.EventID == "" || evt.EventID == evt.TxnID || evt.IsService {
|
if evt.EventID == "" || evt.EventID == evt.TxnID || evt.IsService {
|
||||||
continue
|
continue
|
||||||
} else if currentFound {
|
} else if currentFound {
|
||||||
if !ownMessage || (evt.SenderID == self && evt.Event.Type == mautrix.EventMessage) {
|
if allow == nil || allow(evt.Event) {
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
} else if evt.EventID == current.ID {
|
} else if evt.EventID == current.ID {
|
||||||
@ -492,7 +517,7 @@ func (view *RoomView) EditNext() {
|
|||||||
if view.editing == nil {
|
if view.editing == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
foundMsg := view.findMessage(view.editing, true, true)
|
foundMsg := view.findMessage(view.editing, true, view.filterOwnOnly)
|
||||||
view.SetEditing(foundMsg.GetEvent())
|
view.SetEditing(foundMsg.GetEvent())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -500,7 +525,7 @@ func (view *RoomView) EditPrevious() {
|
|||||||
if view.replying != nil {
|
if view.replying != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
foundMsg := view.findMessage(view.editing, true, false)
|
foundMsg := view.findMessage(view.editing, false, view.filterOwnOnly)
|
||||||
if foundMsg != nil {
|
if foundMsg != nil {
|
||||||
view.SetEditing(foundMsg.GetEvent())
|
view.SetEditing(foundMsg.GetEvent())
|
||||||
}
|
}
|
||||||
@ -511,7 +536,11 @@ func (view *RoomView) SelectNext() {
|
|||||||
if msgView.selected == nil {
|
if msgView.selected == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
foundMsg := view.findMessage(msgView.selected.GetEvent(), false, true)
|
var filter findFilter
|
||||||
|
if view.selectReason == SelectDownload || view.selectReason == SelectOpen {
|
||||||
|
filter = view.filterMediaOnly
|
||||||
|
}
|
||||||
|
foundMsg := view.findMessage(msgView.selected.GetEvent(), true, filter)
|
||||||
if foundMsg != nil {
|
if foundMsg != nil {
|
||||||
msgView.SetSelected(foundMsg)
|
msgView.SetSelected(foundMsg)
|
||||||
// TODO scroll selected message into view
|
// TODO scroll selected message into view
|
||||||
@ -520,7 +549,11 @@ func (view *RoomView) SelectNext() {
|
|||||||
|
|
||||||
func (view *RoomView) SelectPrevious() {
|
func (view *RoomView) SelectPrevious() {
|
||||||
msgView := view.MessageView()
|
msgView := view.MessageView()
|
||||||
foundMsg := view.findMessage(msgView.selected.GetEvent(), false, false)
|
var filter findFilter
|
||||||
|
if view.selectReason == SelectDownload || view.selectReason == SelectOpen {
|
||||||
|
filter = view.filterMediaOnly
|
||||||
|
}
|
||||||
|
foundMsg := view.findMessage(msgView.selected.GetEvent(), false, filter)
|
||||||
if foundMsg != nil {
|
if foundMsg != nil {
|
||||||
msgView.SetSelected(foundMsg)
|
msgView.SetSelected(foundMsg)
|
||||||
// TODO scroll selected message into view
|
// TODO scroll selected message into view
|
||||||
@ -578,6 +611,20 @@ func (view *RoomView) InputSubmit(text string) {
|
|||||||
view.SetInputText("")
|
view.SetInputText("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (view *RoomView) Download(url mautrix.ContentURI, filename string, openFile bool) {
|
||||||
|
path, err := view.parent.matrix.DownloadToDisk(url, filename)
|
||||||
|
if err != nil {
|
||||||
|
view.AddServiceMessage(fmt.Sprintf("Failed to download media: %v", err))
|
||||||
|
view.parent.parent.Render()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
view.AddServiceMessage(fmt.Sprintf("File downloaded to %s", path))
|
||||||
|
view.parent.parent.Render()
|
||||||
|
if openFile {
|
||||||
|
open.Open(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (view *RoomView) Redact(eventID, reason string) {
|
func (view *RoomView) Redact(eventID, reason string) {
|
||||||
defer debug.Recover()
|
defer debug.Recover()
|
||||||
err := view.parent.matrix.Redact(view.Room.ID, eventID, reason)
|
err := view.parent.matrix.Redact(view.Room.ID, eventID, reason)
|
||||||
|
Loading…
Reference in New Issue
Block a user