2020-05-26 06:51:06 +00:00
|
|
|
package service
|
|
|
|
|
|
|
|
import (
|
2020-06-14 18:19:06 +00:00
|
|
|
"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 {
|
2020-06-17 07:06:34 +00:00
|
|
|
// 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)
|
2020-06-13 07:29:32 +00:00
|
|
|
// 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.
|
2020-06-13 07:29:32 +00:00
|
|
|
OnSessionRemove(id string)
|
2020-06-14 18:19:06 +00:00
|
|
|
// OnSessionDisconnect is here to satisfy session's controller.
|
|
|
|
OnSessionDisconnect(id string)
|
2020-06-07 07:06:13 +00:00
|
|
|
}
|
|
|
|
|
2020-06-13 07:29:32 +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)
|
2020-06-20 06:19:25 +00:00
|
|
|
header.SetActive(chrev.GetRevealChild())
|
2020-06-04 23:00:41 +00:00
|
|
|
|
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.
|
2020-06-20 06:19:25 +00:00
|
|
|
header.Connect("clicked", func() {
|
2020-05-28 19:26:55 +00:00
|
|
|
revealed := !chrev.GetRevealChild()
|
|
|
|
chrev.SetRevealChild(revealed)
|
2020-06-20 06:19:25 +00:00
|
|
|
header.SetActive(revealed)
|
2020-05-28 19:26:55 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
// On click, show the auth dialog.
|
2020-06-20 06:19:25 +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.
|
2020-06-14 18:19:06 +00:00
|
|
|
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-20 04:40:34 +00:00
|
|
|
func (c *Container) Sessions() []*session.Row {
|
|
|
|
return c.children.Sessions()
|
|
|
|
}
|
|
|
|
|
2020-06-07 04:27:28 +00:00
|
|
|
func (c *Container) AddSession(ses cchat.Session) *session.Row {
|
|
|
|
srow := session.New(c, ses, c)
|
2020-06-13 07:29:32 +00:00
|
|
|
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 {
|
2020-06-14 18:19:06 +00:00
|
|
|
srow := session.NewLoading(c, id, name, c)
|
2020-06-13 07:29:32 +00:00
|
|
|
c.children.AddSessionRow(id, srow)
|
2020-06-07 04:27:28 +00:00
|
|
|
return srow
|
2020-05-26 06:51:06 +00:00
|
|
|
}
|
|
|
|
|
2020-06-13 07:29:32 +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.
|
2020-06-13 07:29:32 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-06-14 18:19:06 +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.
|
2020-06-14 18:19:06 +00:00
|
|
|
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
|
|
|
|
}
|
2020-06-14 18:19:06 +00:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
2020-06-14 18:19:06 +00:00
|
|
|
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() {
|
2020-06-13 07:29:32 +00:00
|
|
|
var sessions = c.children.Sessions()
|
|
|
|
var ksessions = make([]keyring.Session, 0, len(sessions))
|
2020-06-07 07:06:13 +00:00
|
|
|
|
2020-06-13 07:29:32 +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
|
|
|
}
|
|
|
|
}
|
2020-06-13 07:29:32 +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 {
|
2020-06-20 06:19:25 +00:00
|
|
|
return breadcrumb.Try(nil, c.header.GetText())
|
2020-05-26 06:51:06 +00:00
|
|
|
}
|