cchat-gtk/internal/ui/service/service.go

232 lines
5.6 KiB
Go
Raw Normal View History

2020-05-26 06:51:06 +00:00
package service
import (
"fmt"
2020-05-26 06:51:06 +00:00
"github.com/diamondburned/cchat"
2020-06-07 07:06:13 +00:00
"github.com/diamondburned/cchat-gtk/internal/gts"
2020-06-07 04:27:28 +00:00
"github.com/diamondburned/cchat-gtk/internal/keyring"
2020-06-07 07:06:13 +00:00
"github.com/diamondburned/cchat-gtk/internal/log"
2020-05-28 19:26:55 +00:00
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
2020-06-06 00:47:28 +00:00
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
2020-05-26 06:51:06 +00:00
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
2020-06-07 04:27:28 +00:00
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
2020-05-26 06:51:06 +00:00
"github.com/gotk3/gotk3/gtk"
2020-06-07 07:06:13 +00:00
"github.com/pkg/errors"
2020-05-26 06:51:06 +00:00
)
type View struct {
*gtk.ScrolledWindow
Box *gtk.Box
Services []*Container
}
func NewView() *View {
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
box.Show()
2020-05-28 19:26:55 +00:00
primitives.AddClass(box, "services")
2020-05-26 06:51:06 +00:00
sw, _ := gtk.ScrolledWindowNew(nil, nil)
2020-05-28 19:26:55 +00:00
sw.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
2020-05-26 06:51:06 +00:00
sw.Add(box)
return &View{
sw,
box,
nil,
}
}
2020-06-04 23:00:41 +00:00
func (v *View) AddService(svc cchat.Service, ctrl Controller) *Container {
s := NewContainer(svc, ctrl)
2020-05-26 06:51:06 +00:00
v.Services = append(v.Services, s)
v.Box.Add(s)
2020-06-07 07:06:13 +00:00
// Try and restore all sessions.
s.restoreAllSessions()
2020-06-04 23:00:41 +00:00
return s
2020-05-26 06:51:06 +00:00
}
2020-06-07 07:06:13 +00:00
type Controller interface {
// RowSelected is wrapped around session's MessageRowSelected.
RowSelected(*session.Row, *server.ServerRow, cchat.ServerMessage)
2020-06-07 07:06:13 +00:00
// AuthenticateSession is called to spawn the authentication dialog.
AuthenticateSession(*Container, cchat.Service)
// OnSessionRemove is called to remove a session. This should also clear out
2020-06-07 07:06:13 +00:00
// the message view in the parent package.
OnSessionRemove(id string)
// OnSessionDisconnect is here to satisfy session's controller.
OnSessionDisconnect(id string)
2020-06-07 07:06:13 +00:00
}
// Container represents a single service, including the button header and the
// child containers.
2020-05-26 06:51:06 +00:00
type Container struct {
*gtk.Box
2020-06-07 04:27:28 +00:00
Service cchat.Service
2020-05-26 06:51:06 +00:00
header *header
revealer *gtk.Revealer
children *children
2020-06-07 04:27:28 +00:00
// Embed controller and extend it to override RestoreSession.
Controller
2020-05-26 06:51:06 +00:00
}
2020-06-07 07:06:13 +00:00
// Guarantee that our interface is up-to-date with session's controller.
var _ session.Controller = (*Container)(nil)
2020-06-04 23:00:41 +00:00
func NewContainer(svc cchat.Service, ctrl Controller) *Container {
2020-05-26 06:51:06 +00:00
children := newChildren()
2020-05-28 19:26:55 +00:00
2020-05-26 06:51:06 +00:00
chrev, _ := gtk.RevealerNew()
2020-06-04 23:00:41 +00:00
chrev.SetRevealChild(true)
2020-05-26 06:51:06 +00:00
chrev.Add(children)
2020-05-28 19:26:55 +00:00
chrev.Show()
2020-05-26 06:51:06 +00:00
2020-06-04 23:00:41 +00:00
header := newHeader(svc)
header.reveal.SetActive(chrev.GetRevealChild())
2020-05-26 06:51:06 +00:00
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
box.Show()
box.PackStart(header, false, false, 0)
box.PackStart(chrev, false, false, 0)
2020-05-28 19:26:55 +00:00
primitives.AddClass(box, "service")
2020-05-26 06:51:06 +00:00
2020-06-07 04:27:28 +00:00
container := &Container{
Box: box,
Service: svc,
header: header,
revealer: chrev,
children: children,
Controller: ctrl,
}
2020-05-28 19:26:55 +00:00
// On click, toggle reveal.
header.reveal.Connect("clicked", func() {
revealed := !chrev.GetRevealChild()
chrev.SetRevealChild(revealed)
header.reveal.SetActive(revealed)
})
// On click, show the auth dialog.
2020-05-26 06:51:06 +00:00
header.add.Connect("clicked", func() {
2020-06-04 23:00:41 +00:00
ctrl.AuthenticateSession(container, svc)
2020-05-26 06:51:06 +00:00
})
2020-06-07 04:27:28 +00:00
// Make menu items.
primitives.AppendMenuItems(header.Menu, []gtk.IMenuItem{
2020-06-07 07:06:13 +00:00
primitives.MenuItem("Save Sessions", func() {
container.SaveAllSessions()
}),
2020-06-07 04:27:28 +00:00
})
2020-05-26 06:51:06 +00:00
return container
}
2020-06-07 04:27:28 +00:00
func (c *Container) AddSession(ses cchat.Session) *session.Row {
srow := session.New(c, ses, c)
c.children.AddSessionRow(ses.ID(), srow)
2020-06-07 07:06:13 +00:00
c.SaveAllSessions()
2020-06-07 04:27:28 +00:00
return srow
2020-06-04 23:00:41 +00:00
}
2020-06-07 04:27:28 +00:00
func (c *Container) AddLoadingSession(id, name string) *session.Row {
srow := session.NewLoading(c, id, name, c)
c.children.AddSessionRow(id, srow)
2020-06-07 04:27:28 +00:00
return srow
2020-05-26 06:51:06 +00:00
}
func (c *Container) RemoveSession(row *session.Row) {
var id = row.Session.ID()
c.children.RemoveSessionRow(id)
2020-06-07 07:06:13 +00:00
c.SaveAllSessions()
// Call the parent's method.
c.Controller.OnSessionRemove(id)
}
func (c *Container) MoveSession(rowID, beneathRowID string) {
c.children.MoveSession(rowID, beneathRowID)
c.SaveAllSessions()
2020-06-07 07:06:13 +00:00
}
func (c *Container) OnSessionDisconnect(ses *session.Row) {
c.Controller.OnSessionDisconnect(ses.ID())
}
2020-06-07 07:06:13 +00:00
// RestoreSession tries to restore sessions asynchronously. This satisfies
// session.Controller.
func (c *Container) RestoreSession(row *session.Row, id string) {
2020-06-07 07:06:13 +00:00
// Can this session be restored? If not, exit.
restorer, ok := c.Service.(cchat.SessionRestorer)
if !ok {
return
}
// Do we even have a session stored?
krs := keyring.RestoreSession(c.Service.Name(), id)
if krs == nil {
log.Error(fmt.Errorf(
"Missing keyring for service %s, session ID %s",
c.Service.Name().Content, id,
))
return
}
c.restoreSession(row, restorer, *krs)
2020-06-07 07:06:13 +00:00
}
// internal method called on AddService.
func (c *Container) restoreAllSessions() {
// Can this session be restored? If not, exit.
restorer, ok := c.Service.(cchat.SessionRestorer)
if !ok {
return
}
var sessions = keyring.RestoreSessions(c.Service.Name())
for _, krs := range sessions {
// Copy the session to avoid race conditions.
krs := krs
row := c.AddLoadingSession(krs.ID, krs.Name)
c.restoreSession(row, restorer, krs)
}
}
func (c *Container) restoreSession(r *session.Row, res cchat.SessionRestorer, k keyring.Session) {
go func() {
s, err := res.RestoreSession(k.Data)
if err != nil {
err = errors.Wrapf(err, "Failed to restore session %s (%s)", k.ID, k.Name)
log.Error(err)
gts.ExecAsync(func() { r.SetFailed(err) })
2020-06-07 07:06:13 +00:00
} else {
gts.ExecAsync(func() { r.SetSession(s) })
}
}()
}
func (c *Container) SaveAllSessions() {
var sessions = c.children.Sessions()
var ksessions = make([]keyring.Session, 0, len(sessions))
2020-06-07 07:06:13 +00:00
for _, s := range sessions {
2020-06-07 04:27:28 +00:00
if k := s.KeyringSession(); k != nil {
ksessions = append(ksessions, *k)
2020-06-06 07:44:36 +00:00
}
}
keyring.SaveSessions(c.Service.Name(), ksessions)
2020-05-26 06:51:06 +00:00
}
2020-06-07 04:27:28 +00:00
func (c *Container) Breadcrumb() breadcrumb.Breadcrumb {
return breadcrumb.Try(nil, c.header.reveal.GetText())
2020-05-26 06:51:06 +00:00
}