Compare commits

...

9 Commits

Author SHA1 Message Date
diamondburned b5bb0c9bb9 Revert "Replace stop callbacks with contexts"
This reverts commit 410ac73469.

The rationale is that the frontend can wrap its own components with a
disposable thread-safe wrapper that doesn't work once it's invalidated
if it wants the guarantee that the component doesn't work anymore once
the context is stopped.
2021-05-04 16:19:13 -07:00
diamondburned 8bfabf58ec Make SetterMethods ContainerUpdaterMethods
This commit separated SetterMethods that are specifically for updating
containers to another method type, named ContainerUpdaterMethod. This
change is done to force a context parameter into container setters,
allowing the frontend to know if an incoming update is valid or not,
based on the state of the context given.

The validity check should be the same as any other context:

    select {
    case <-ctx.Done():
        return
    default:
        addEvent()
    }

It is crucial, however, to do the checking and updating in the same
thread or lock as the context is cancelled. This explicit
synchronization is required to prevent any race condition whatsoever
with cancellation of the context.

The backend must pass in the right context, that is, any context that
inherits the cancellation from the frontend. Passing in the invalid
context is undefined behavior and will eventually cause a data race.
2021-05-01 21:41:39 -07:00
diamondburned 410ac73469 Replace stop callbacks with contexts
This commit removes all stop callbacks in ContainerMethods. The
intention is to have backends disconnect callbacks when the context is
cancelled, rather than when the stop function is called.

This helps get rid of countless race condition flaws caused by the
duration between the context being cancelled on one thread and the stop
callback being set in another, causing the handlers to not disconnect.
2021-05-01 17:49:25 -07:00
diamondburned 4e11444f6c Configurator to be SetterMethods
This commit changes Configurator's methods to be SetterMethods instead
of IOMethods, as Configurator is specifically made for frontend-managed
settings just for the backend, so no storing/loading is needed on the
backend's side.

This commit also changes SetterMethod to allow methods done to the
backend to error out, in case the setting value is invalid somehow.
Setter methods that are called by the backend (as opposed to the
frontend) must never error.
2021-05-01 17:22:01 -07:00
diamondburned 86956a65ec Allow cancels for ContainerMethods and IOMethods
This commit changes several ContainerMethods to take in a context. It
also changes all IOMethods to take in a context.

This addition adds consistency to the API as well as allowing better
graceful cancellation and cleanups if needed when, for example, the user
wants to discard an ongoing process.
2021-05-01 17:02:02 -07:00
diamondburned f2de1cb84d Added missing text.Rich return in ListMember 2021-03-25 16:25:44 -07:00
diamondburned 0cb14b9819 ListMember to no longer use Namer
This commit broke ListMember to remove the Namer interface. This is
because the whole interface should act as a static container with
information to be updated.
2021-03-25 16:05:35 -07:00
diamondburned f24feb2002 MessageUpdate should only update the content
This commit changes MessageUpdate so that it only updates the message
content. Updating the username should be up to MessageCreate's Author.
2021-03-20 00:13:39 -07:00
diamondburned f8c644fa7e Allow empty texts with segments
This commit allows segments in an empty text segment to account for
segments with only an image.
2021-03-19 22:40:31 -07:00
6 changed files with 142 additions and 84 deletions

View File

@ -176,7 +176,7 @@ type Actioner interface {
// Do executes a message action on the given messageID, which would be taken // Do executes a message action on the given messageID, which would be taken
// from MessageHeader.ID(). This method is allowed to do IO; the frontend should // from MessageHeader.ID(). This method is allowed to do IO; the frontend should
// take care of running it asynchronously. // take care of running it asynchronously.
Do(action string, id ID) error // Blocking Do(ctx context.Context, action string, id ID) error // Blocking
// MessageActions returns a list of possible actions to a message in pretty // MessageActions returns a list of possible actions to a message in pretty
// strings that the frontend will use to directly display. This method must not // strings that the frontend will use to directly display. This method must not
// do IO. // do IO.
@ -217,7 +217,7 @@ type AuthenticateError interface {
type Authenticator interface { type Authenticator interface {
// Authenticate will be called with a list of values with indices correspond to // Authenticate will be called with a list of values with indices correspond to
// the returned slice of AuthenticateEntry. // the returned slice of AuthenticateEntry.
Authenticate([]string) (Session, AuthenticateError) // Blocking Authenticate(context.Context, []string) (Session, AuthenticateError) // Blocking
// AuthenticateForm should return a list of authentication entries for the // AuthenticateForm should return a list of authentication entries for the
// frontend to render. // frontend to render.
AuthenticateForm() []AuthenticateEntry AuthenticateForm() []AuthenticateEntry
@ -295,7 +295,7 @@ type Commander interface {
// A helper function for this kind of behavior is available in package split, // A helper function for this kind of behavior is available in package split,
// under the ArgsIndexed function. This implementation also provides the rough // under the ArgsIndexed function. This implementation also provides the rough
// specifications. // specifications.
Run(words []string) ([]byte, error) // Blocking Run(ctx context.Context, words []string) ([]byte, error) // Blocking
// Asserters. // Asserters.
@ -318,19 +318,17 @@ type Completer interface {
} }
// Configurator is an interface which the backend can implement for a primitive // Configurator is an interface which the backend can implement for a primitive
// configuration API. Since these methods do return an error, they are allowed // configuration API.
// to do IO. The frontend should handle this appropriately, including running
// them asynchronously.
type Configurator interface { type Configurator interface {
SetConfiguration(map[string]string) error // Blocking SetConfiguration(map[string]string) error
Configuration() (map[string]string, error) // Blocking Configuration() map[string]string
} }
// Editor adds message editing to the messenger. Only EditMessage can do IO. // Editor adds message editing to the messenger. Only EditMessage can do IO.
type Editor interface { type Editor interface {
// Edit edits the message with the given ID to the given content, which is the // Edit edits the message with the given ID to the given content, which is the
// edited string from RawMessageContent. This method can do IO. // edited string from RawMessageContent. This method can do IO.
Edit(id ID, content string) error // Blocking Edit(ctx context.Context, id ID, content string) error // Blocking
// RawContent gets the original message text for editing. This method must not // RawContent gets the original message text for editing. This method must not
// do IO. // do IO.
RawContent(id ID) (string, error) RawContent(id ID) (string, error)
@ -357,17 +355,17 @@ type Identifier interface {
// Labels given to the frontend may contain images or avatars, and the frontend // Labels given to the frontend may contain images or avatars, and the frontend
// has the choice to display them or not. // has the choice to display them or not.
type LabelContainer interface { type LabelContainer interface {
SetLabel(text.Rich) SetLabel(context.Context, text.Rich)
} }
// ListMember represents a single member in the member list. This is a base // ListMember represents a single member in the member list. Note that this
// interface that may implement more interfaces, such as Iconer for the user's // interface should be treated as a static container: updating a member will
// avatar. // involve a completely new ListMember instance with the same ID.
// //
// Note that the frontend may give everyone an avatar regardless, or it may not // Note that the frontend may give everyone an avatar regardless, or it may not
// show any avatars at all. // show any avatars at all.
type ListMember interface { type ListMember interface {
User Identifier
// Secondary returns the subtext of this member. This could be anything, such as // Secondary returns the subtext of this member. This could be anything, such as
// a user's custom status or away reason. // a user's custom status or away reason.
@ -376,6 +374,9 @@ type ListMember interface {
// offline members with the offline status if it doesn't want to show offline // offline members with the offline status if it doesn't want to show offline
// menbers at all. // menbers at all.
Status() Status Status() Status
// Name returns the username or the nickname of the member, whichever the
// backend should prefer.
Name() text.Rich
} }
// Lister is for servers that contain children servers. This is similar to // Lister is for servers that contain children servers. This is similar to
@ -418,14 +419,14 @@ type MemberDynamicSection interface {
// The client can call this method exactly as many times as it has called // The client can call this method exactly as many times as it has called
// LoadMore. However, false should be returned if the client should stop, and // LoadMore. However, false should be returned if the client should stop, and
// future calls without LoadMore should still return false. // future calls without LoadMore should still return false.
LoadLess() bool // Blocking LoadLess(context.Context) bool // Blocking
// LoadMore is a method which the client can call to ask for more members. This // LoadMore is a method which the client can call to ask for more members. This
// method can do IO. // method can do IO.
// //
// Clients may call this method on the last section in the section slice; // Clients may call this method on the last section in the section slice;
// however, calling this method on any section is allowed. Clients may not call // however, calling this method on any section is allowed. Clients may not call
// this method if the number of members in this section is equal to Total. // this method if the number of members in this section is equal to Total.
LoadMore() bool // Blocking LoadMore(context.Context) bool // Blocking
} }
// MemberListContainer is a generic interface for any container that can display // MemberListContainer is a generic interface for any container that can display
@ -451,7 +452,7 @@ type MemberDynamicSection interface {
type MemberListContainer interface { type MemberListContainer interface {
// RemoveMember removes a member from a section. If neither the member nor the // RemoveMember removes a member from a section. If neither the member nor the
// section exists, then the client should ignore it. // section exists, then the client should ignore it.
RemoveMember(sectionID ID, memberID ID) RemoveMember(ctx context.Context, sectionID ID, memberID ID)
// SetMember adds or updates (or upsert) a member into a section. This operation // SetMember adds or updates (or upsert) a member into a section. This operation
// must not change the section's member count. As such, changes should be done // must not change the section's member count. As such, changes should be done
// separately in SetSection. If the section does not exist, then the client // separately in SetSection. If the section does not exist, then the client
@ -461,12 +462,12 @@ type MemberListContainer interface {
// Typically, the backend should try and avoid calling this method and instead // Typically, the backend should try and avoid calling this method and instead
// update the labeler in the name. This method should only be used for adding // update the labeler in the name. This method should only be used for adding
// members. // members.
SetMember(sectionID ID, member ListMember) SetMember(ctx context.Context, sectionID ID, member ListMember)
// SetSections (re)sets the list of sections to be the given slice. Members from // SetSections (re)sets the list of sections to be the given slice. Members from
// the old section list should be transferred over to the new section entry if // the old section list should be transferred over to the new section entry if
// the section name's content is the same. Old sections that don't appear in the // the section name's content is the same. Old sections that don't appear in the
// new slice should be removed. // new slice should be removed.
SetSections(sections []MemberSection) SetSections(ctx context.Context, sections []MemberSection)
} }
// MemberLister adds a member list into a message server. // MemberLister adds a member list into a message server.
@ -519,11 +520,13 @@ type MessageHeader interface {
Time() time.Time Time() time.Time
} }
// MessageUpdate is the interface for a message update (or edit) event. It // MessageUpdate is the interface for a message update (or edit) event. It is
// behaves similarly to MessageCreate, except all fields are optional. The // only responsible for updating a message's content. The author's name should
// frontend is responsible for checking which field is not empty and check it. // be updated using MessageCreate's Author.
type MessageUpdate interface { type MessageUpdate interface {
MessageCreate MessageHeader
Content() text.Rich
} }
// MessagesContainer is a view implementation that displays a list of messages // MessagesContainer is a view implementation that displays a list of messages
@ -534,12 +537,12 @@ type MessageUpdate interface {
// allowed to have multiple views. This is usually done with tabs or splits, but // allowed to have multiple views. This is usually done with tabs or splits, but
// the backend should update them all nonetheless. // the backend should update them all nonetheless.
type MessagesContainer interface { type MessagesContainer interface {
DeleteMessage(MessageDelete) DeleteMessage(context.Context, MessageDelete)
UpdateMessage(MessageUpdate) UpdateMessage(context.Context, MessageUpdate)
// CreateMessage inserts a message into the container. The frontend must // CreateMessage inserts a message into the container. The frontend must
// guarantee that the messages are in order based on what's returned from // guarantee that the messages are in order based on what's returned from
// Time(). // Time().
CreateMessage(MessageCreate) CreateMessage(context.Context, MessageCreate)
} }
// Messenger is for servers that contain messages. This is similar to Discord or // Messenger is for servers that contain messages. This is similar to Discord or
@ -615,10 +618,10 @@ type ReadContainer interface {
// their read indicators. The backend can use this to free up users/authors that // their read indicators. The backend can use this to free up users/authors that
// are no longer in the server, for example when they are offline or have left // are no longer in the server, for example when they are offline or have left
// the server. // the server.
DeleteIndications(authorIDs []ID) DeleteIndications(ctx context.Context, authorIDs []ID)
// AddIndications adds a map of users/authors to the respective message ID of // AddIndications adds a map of users/authors to the respective message ID of
// the server that implements ReadIndicator. // the server that implements ReadIndicator.
AddIndications([]ReadIndication) AddIndications(context.Context, []ReadIndication)
} }
// ReadIndicator adds a read indicator API for frontends to show. An example of // ReadIndicator adds a read indicator API for frontends to show. An example of
@ -629,7 +632,7 @@ type ReadIndicator interface {
// must keep track of which read states to send over to not overwhelm the // must keep track of which read states to send over to not overwhelm the
// frontend, and the frontend must either keep track of them, or it should not // frontend, and the frontend must either keep track of them, or it should not
// display it at all. // display it at all.
ReadIndicate(ReadContainer) (stop func(), err error) ReadIndicate(context.Context, ReadContainer) (stop func(), err error)
} }
// Replier indicates that the message being sent is a reply to something. // Replier indicates that the message being sent is a reply to something.
@ -663,7 +666,7 @@ type Sender interface {
// CanAttach returns whether or not the client is allowed to upload files. // CanAttach returns whether or not the client is allowed to upload files.
CanAttach() bool CanAttach() bool
// Send is called by the frontend to send a message to this channel. // Send is called by the frontend to send a message to this channel.
Send(SendableMessage) error // Blocking Send(context.Context, SendableMessage) error // Blocking
// Asserters. // Asserters.
@ -718,7 +721,7 @@ type ServerUpdate interface {
// as servers can be infinitely nested. Frontends should also reset the entire // as servers can be infinitely nested. Frontends should also reset the entire
// node and its children when SetServers is called again. // node and its children when SetServers is called again.
type ServersContainer interface { type ServersContainer interface {
UpdateServer(ServerUpdate) UpdateServer(context.Context, ServerUpdate)
// SetServer is called by the backend service to request a reset of the server // SetServer is called by the backend service to request a reset of the server
// list. The frontend can choose to call Servers() on each of the given servers, // list. The frontend can choose to call Servers() on each of the given servers,
// or it can call that later. The backend should handle both cases. // or it can call that later. The backend should handle both cases.
@ -728,7 +731,7 @@ type ServersContainer interface {
// should only be considered empty if it's an empty non-nil slice. An // should only be considered empty if it's an empty non-nil slice. An
// unavailable list, on the other hand, can be treated as backend issues, e.g. a // unavailable list, on the other hand, can be treated as backend issues, e.g. a
// connection issue. // connection issue.
SetServers([]Server) SetServers(context.Context, []Server)
} }
// Service is a complete service that's capable of multiple sessions. It has to // Service is a complete service that's capable of multiple sessions. It has to
@ -791,7 +794,7 @@ type Session interface {
// When this function fails, the frontend may display the error upfront. // When this function fails, the frontend may display the error upfront.
// However, it will treat the session as actually disconnected. If needed, the // However, it will treat the session as actually disconnected. If needed, the
// backend must implement reconnection by itself. // backend must implement reconnection by itself.
Disconnect() error // Blocking, Disposer Disconnect(context.Context) error // Blocking, Disposer
// Asserters. // Asserters.
@ -805,7 +808,7 @@ type Session interface {
// //
// To save a session, refer to SessionSaver. // To save a session, refer to SessionSaver.
type SessionRestorer interface { type SessionRestorer interface {
RestoreSession(map[string]string) (Session, error) // Blocking RestoreSession(context.Context, map[string]string) (Session, error) // Blocking
} }
// SessionSaver extends Session and is called by the frontend to save the // SessionSaver extends Session and is called by the frontend to save the
@ -832,11 +835,11 @@ type TypingContainer interface {
// of typers. This function is usually not needed, as the client will take care // of typers. This function is usually not needed, as the client will take care
// of removing them after TypingTimeout has been reached or other conditions // of removing them after TypingTimeout has been reached or other conditions
// listed in ServerMessageTypingIndicator are met. // listed in ServerMessageTypingIndicator are met.
RemoveTyper(authorID ID) RemoveTyper(ctx context.Context, authorID ID)
// AddTyper appends the typer (author) into the frontend's list of typers, or it // AddTyper appends the typer (author) into the frontend's list of typers, or it
// pushes this typer on top of others. The frontend should assume current time // pushes this typer on top of others. The frontend should assume current time
// every time AddTyper is called. // every time AddTyper is called.
AddTyper(User) AddTyper(context.Context, User)
} }
// TypingIndicator optionally extends ServerMessage to provide bidirectional // TypingIndicator optionally extends ServerMessage to provide bidirectional
@ -854,7 +857,7 @@ type TypingIndicator interface {
// This method does not take in a context, as it's supposed to only use event // This method does not take in a context, as it's supposed to only use event
// handlers and not do any IO calls. Nonetheless, the client must treat it like // handlers and not do any IO calls. Nonetheless, the client must treat it like
// it does and call it asynchronously. // it does and call it asynchronously.
TypingSubscribe(TypingContainer) (stop func(), err error) TypingSubscribe(context.Context, TypingContainer) (stop func(), err error)
// TypingTimeout returns the interval between typing events sent by the client // TypingTimeout returns the interval between typing events sent by the client
// as well as the timeout before the client should remove the typer. Typically, // as well as the timeout before the client should remove the typer. Typically,
// a constant should be returned. // a constant should be returned.
@ -863,7 +866,7 @@ type TypingIndicator interface {
// function can do IO calls, and the client must take care of calling it in a // function can do IO calls, and the client must take care of calling it in a
// goroutine (or an asynchronous queue) as well as throttling it to // goroutine (or an asynchronous queue) as well as throttling it to
// TypingTimeout. // TypingTimeout.
Typing() error // Blocking Typing(context.Context) error // Blocking
} }
// UnreadContainer is an interface that a single server container (such as a // UnreadContainer is an interface that a single server container (such as a
@ -882,7 +885,7 @@ type TypingIndicator interface {
type UnreadContainer interface { type UnreadContainer interface {
// SetUnread sets the container's unread state to the given boolean. The // SetUnread sets the container's unread state to the given boolean. The
// frontend may choose how to represent this. // frontend may choose how to represent this.
SetUnread(unread bool, mentioned bool) SetUnread(ctx context.Context, unread bool, mentioned bool)
} }
// UnreadIndicator adds an unread state API for frontends to use. The unread // UnreadIndicator adds an unread state API for frontends to use. The unread
@ -896,7 +899,7 @@ type UnreadIndicator interface {
// //
// This function must provide a way to remove callbacks, as clients must call // This function must provide a way to remove callbacks, as clients must call
// this when the old server is destroyed, such as when Servers is called. // this when the old server is destroyed, such as when Servers is called.
UnreadIndicate(UnreadContainer) (stop func(), err error) UnreadIndicate(context.Context, UnreadContainer) (stop func(), err error)
// MarkRead marks a message in the server messenger as read. Backends that // MarkRead marks a message in the server messenger as read. Backends that
// implement the UnreadIndicator interface must give control of marking messages // implement the UnreadIndicator interface must give control of marking messages
// as read to the frontend if possible. // as read to the frontend if possible.
@ -905,7 +908,7 @@ type UnreadIndicator interface {
// the frontend has no use in knowing the error. As such, marking messages as // the frontend has no use in knowing the error. As such, marking messages as
// read is best-effort. The backend is in charge of synchronizing the read state // read is best-effort. The backend is in charge of synchronizing the read state
// with the server and coordinating it with reasonable rate limits, if needed. // with the server and coordinating it with reasonable rate limits, if needed.
MarkRead(messageID ID) MarkRead(ctx context.Context, messageID ID)
} }
// User is the interface for an identifiable author. The interface defines that // User is the interface for an identifiable author. The interface defines that

View File

@ -66,9 +66,13 @@ func generateInterfaces(ifaces []repository.Interface) jen.Code {
stmt.Params(generateFuncParams(method.Returns, method.ErrorType)...) stmt.Params(generateFuncParams(method.Returns, method.ErrorType)...)
case repository.SetterMethod: case repository.SetterMethod:
stmt.Params(generateFuncParams(method.Parameters, "")...) stmt.Params(generateFuncParams(method.Parameters, "")...)
stmt.Params(generateFuncParamsErr(repository.NamedType{}, method.ErrorType)...)
case repository.ContainerUpdaterMethod:
stmt.Params(generateFuncParamsCtx(method.Parameters, "")...)
stmt.Params(generateFuncParamsErr(repository.NamedType{}, method.ErrorType)...)
case repository.IOMethod: case repository.IOMethod:
stmt.Params(generateFuncParams(method.Parameters, "")...) stmt.Params(generateFuncParamsCtx(method.Parameters, "")...)
stmt.Params(generateFuncParamErr(method.ReturnValue, method.ErrorType)...) stmt.Params(generateFuncParamsErr(method.ReturnValue, method.ErrorType)...)
var comment = "Blocking" var comment = "Blocking"
if method.Disposer { if method.Disposer {
comment += ", Disposer" comment += ", Disposer"
@ -96,7 +100,7 @@ func generateInterfaces(ifaces []repository.Interface) jen.Code {
return stmt return stmt
} }
func generateFuncParamErr(param repository.NamedType, errorType string) []jen.Code { func generateFuncParamsErr(param repository.NamedType, errorType string) []jen.Code {
stmt := make([]jen.Code, 0, 2) stmt := make([]jen.Code, 0, 2)
if !param.IsZero() { if !param.IsZero() {
@ -121,6 +125,18 @@ func generateFuncParam(param repository.NamedType) jen.Code {
return jen.Id(param.Name).Add(genutils.GenerateType(param)) return jen.Id(param.Name).Add(genutils.GenerateType(param))
} }
func generateFuncParamsCtx(params []repository.NamedType, errorType string) []jen.Code {
var name string
if len(params) > 0 && params[0].Name != "" {
name = "ctx"
}
p := []repository.NamedType{{Name: name, Type: "context.Context"}}
p = append(p, params...)
return generateFuncParams(p, errorType)
}
func generateFuncParams(params []repository.NamedType, errorType string) []jen.Code { func generateFuncParams(params []repository.NamedType, errorType string) []jen.Code {
if len(params) == 0 { if len(params) == 0 {
return nil return nil

Binary file not shown.

View File

@ -10,6 +10,7 @@ func init() {
gob.Register(AsserterMethod{}) gob.Register(AsserterMethod{})
gob.Register(GetterMethod{}) gob.Register(GetterMethod{})
gob.Register(SetterMethod{}) gob.Register(SetterMethod{})
gob.Register(ContainerUpdaterMethod{})
gob.Register(IOMethod{}) gob.Register(IOMethod{})
} }
@ -67,21 +68,39 @@ func (m GetterMethod) ReturnError() bool {
} }
// SetterMethod is a method that sets values. These methods must not do IO, and // SetterMethod is a method that sets values. These methods must not do IO, and
// they have to be non-blocking. They're used only for containers. Actual setter // they have to be non-blocking.
// methods implemented by the backend belongs to IOMethods.
//
// Clients should always keep track of the values given to setters and free them
// once the setters are called again with new values.
type SetterMethod struct { type SetterMethod struct {
method method
// Parameters is the list of parameters in the function. These parameters // Parameters is the list of parameters in the function. These parameters
// should be the parameters to set. // should be the parameters to set.
Parameters []NamedType Parameters []NamedType
// ErrorType is non-empty if the function returns an error at the end of
// returns. An error may be returned from the backend if the input is
// invalid, but it must not do IO. Frontend setters must never error.
ErrorType string
}
// ContainerUpdaterMethod is a SetterMethod that passes to the container the
// current context to prevent race conditions when synchronizing.
// The rule of thumb is that any setter method done inside a method with a
// context is usually this type of method.
type ContainerUpdaterMethod struct {
method
// Parameters is the list of parameters in the function. These parameters
// should be the parameters to set.
Parameters []NamedType
// ErrorType is non-empty if the function returns an error at the end of
// returns. An error may be returned from the backend if the input is
// invalid, but it must not do IO. Frontend setters must never error.
ErrorType string
} }
// IOMethod is a regular method that can do IO and thus is blocking. These // IOMethod is a regular method that can do IO and thus is blocking. These
// methods usually always return errors. // methods usually always return errors. IOMethods must always have means of
// cancelling them in the API, but implementations don't have to use it; as
// such, the user should always have a timeout to gracefully wait.
type IOMethod struct { type IOMethod struct {
method method

View File

@ -817,18 +817,15 @@ var Main = Packages{
}, { }, {
Comment: Comment{` Comment: Comment{`
Configurator is an interface which the backend can implement for a Configurator is an interface which the backend can implement for a
primitive configuration API. Since these methods do return an error, primitive configuration API.
they are allowed to do IO. The frontend should handle this
appropriately, including running them asynchronously.
`}, `},
Name: "Configurator", Name: "Configurator",
Methods: []Method{ Methods: []Method{
IOMethod{ GetterMethod{
method: method{Name: "Configuration"}, method: method{Name: "Configuration"},
ReturnValue: NamedType{Type: "map[string]string"}, Returns: []NamedType{{Type: "map[string]string"}},
ErrorType: "error",
}, },
IOMethod{ SetterMethod{
method: method{Name: "SetConfiguration"}, method: method{Name: "SetConfiguration"},
Parameters: []NamedType{{Type: "map[string]string"}}, Parameters: []NamedType{{Type: "map[string]string"}},
ErrorType: "error", ErrorType: "error",
@ -1251,7 +1248,6 @@ var Main = Packages{
Name: "Backlog", Name: "Backlog",
}, },
Parameters: []NamedType{ Parameters: []NamedType{
{"ctx", "context.Context"},
{"before", "ID"}, {"before", "ID"},
{"msgc", "MessagesContainer"}, {"msgc", "MessagesContainer"},
}, },
@ -1302,6 +1298,7 @@ var Main = Packages{
`}, `},
Name: "ReadIndicate", Name: "ReadIndicate",
}, },
HasContext: true,
ContainerType: "ReadContainer", ContainerType: "ReadContainer",
}, },
}, },
@ -1314,7 +1311,7 @@ var Main = Packages{
`}, `},
Name: "UnreadIndicator", Name: "UnreadIndicator",
Methods: []Method{ Methods: []Method{
SetterMethod{ ContainerUpdaterMethod{
method: method{ method: method{
Comment: Comment{` Comment: Comment{`
MarkRead marks a message in the server messenger as MarkRead marks a message in the server messenger as
@ -1348,6 +1345,7 @@ var Main = Packages{
`}, `},
Name: "UnreadIndicate", Name: "UnreadIndicate",
}, },
HasContext: true,
ContainerType: "UnreadContainer", ContainerType: "UnreadContainer",
}, },
}, },
@ -1403,6 +1401,7 @@ var Main = Packages{
`}, `},
Name: "TypingSubscribe", Name: "TypingSubscribe",
}, },
HasContext: true,
ContainerType: "TypingContainer", ContainerType: "TypingContainer",
}, },
}, },
@ -1453,7 +1452,7 @@ var Main = Packages{
`}, `},
Name: "ServersContainer", Name: "ServersContainer",
Methods: []Method{ Methods: []Method{
SetterMethod{ ContainerUpdaterMethod{
method: method{ method: method{
Comment: Comment{` Comment: Comment{`
SetServer is called by the backend service to SetServer is called by the backend service to
@ -1474,7 +1473,7 @@ var Main = Packages{
}, },
Parameters: []NamedType{{Type: "[]Server"}}, Parameters: []NamedType{{Type: "[]Server"}},
}, },
SetterMethod{ ContainerUpdaterMethod{
method: method{Name: "UpdateServer"}, method: method{Name: "UpdateServer"},
Parameters: []NamedType{{Type: "ServerUpdate"}}, Parameters: []NamedType{{Type: "ServerUpdate"}},
}, },
@ -1534,7 +1533,7 @@ var Main = Packages{
`}, `},
Name: "MessagesContainer", Name: "MessagesContainer",
Methods: []Method{ Methods: []Method{
SetterMethod{ ContainerUpdaterMethod{
method: method{ method: method{
Comment: Comment{` Comment: Comment{`
CreateMessage inserts a message into the container. CreateMessage inserts a message into the container.
@ -1545,11 +1544,11 @@ var Main = Packages{
}, },
Parameters: []NamedType{{Type: "MessageCreate"}}, Parameters: []NamedType{{Type: "MessageCreate"}},
}, },
SetterMethod{ ContainerUpdaterMethod{
method: method{Name: "UpdateMessage"}, method: method{Name: "UpdateMessage"},
Parameters: []NamedType{{Type: "MessageUpdate"}}, Parameters: []NamedType{{Type: "MessageUpdate"}},
}, },
SetterMethod{ ContainerUpdaterMethod{
method: method{Name: "DeleteMessage"}, method: method{Name: "DeleteMessage"},
Parameters: []NamedType{{Type: "MessageDelete"}}, Parameters: []NamedType{{Type: "MessageDelete"}},
}, },
@ -1602,12 +1601,20 @@ var Main = Packages{
}, { }, {
Comment: Comment{` Comment: Comment{`
MessageUpdate is the interface for a message update (or edit) MessageUpdate is the interface for a message update (or edit)
event. It behaves similarly to MessageCreate, except all fields event. It is only responsible for updating a message's content.
are optional. The frontend is responsible for checking which The author's name should be updated using MessageCreate's
field is not empty and check it. Author.
`}, `},
Name: "MessageUpdate", Name: "MessageUpdate",
Embeds: []EmbeddedInterface{{InterfaceName: "MessageCreate"}}, Embeds: []EmbeddedInterface{{InterfaceName: "MessageHeader"}},
Methods: []Method{
GetterMethod{
method: method{Name: "Content"},
Returns: []NamedType{{
Type: MakeQual("text", "Rich"),
}},
},
},
}, { }, {
Comment: Comment{` Comment: Comment{`
MessageDelete is the interface for a message delete event. MessageDelete is the interface for a message delete event.
@ -1630,7 +1637,7 @@ var Main = Packages{
`}, `},
Name: "LabelContainer", Name: "LabelContainer",
Methods: []Method{ Methods: []Method{
SetterMethod{ ContainerUpdaterMethod{
method: method{Name: "SetLabel"}, method: method{Name: "SetLabel"},
Parameters: []NamedType{{ Parameters: []NamedType{{
Type: MakeQual("text", "Rich"), Type: MakeQual("text", "Rich"),
@ -1646,7 +1653,7 @@ var Main = Packages{
`}, `},
Name: "ReadContainer", Name: "ReadContainer",
Methods: []Method{ Methods: []Method{
SetterMethod{ ContainerUpdaterMethod{
method: method{ method: method{
Comment: Comment{` Comment: Comment{`
AddIndications adds a map of users/authors to the AddIndications adds a map of users/authors to the
@ -1657,7 +1664,7 @@ var Main = Packages{
}, },
Parameters: []NamedType{{"", "[]ReadIndication"}}, Parameters: []NamedType{{"", "[]ReadIndication"}},
}, },
SetterMethod{ ContainerUpdaterMethod{
method: method{ method: method{
Comment: Comment{` Comment: Comment{`
DeleteIndications deletes a list of unused DeleteIndications deletes a list of unused
@ -1691,7 +1698,7 @@ var Main = Packages{
`}, `},
Name: "UnreadContainer", Name: "UnreadContainer",
Methods: []Method{ Methods: []Method{
SetterMethod{ ContainerUpdaterMethod{
method: method{ method: method{
Comment: Comment{` Comment: Comment{`
SetUnread sets the container's unread state to the SetUnread sets the container's unread state to the
@ -1717,7 +1724,7 @@ var Main = Packages{
`}, `},
Name: "TypingContainer", Name: "TypingContainer",
Methods: []Method{ Methods: []Method{
SetterMethod{ ContainerUpdaterMethod{
method: method{ method: method{
Comment: Comment{` Comment: Comment{`
AddTyper appends the typer (author) into the AddTyper appends the typer (author) into the
@ -1729,7 +1736,7 @@ var Main = Packages{
}, },
Parameters: []NamedType{{"", "User"}}, Parameters: []NamedType{{"", "User"}},
}, },
SetterMethod{ ContainerUpdaterMethod{
method: method{ method: method{
Comment: Comment{` Comment: Comment{`
RemoveTyper explicitly removes the typer with the RemoveTyper explicitly removes the typer with the
@ -1769,7 +1776,7 @@ var Main = Packages{
`}, `},
Name: "MemberListContainer", Name: "MemberListContainer",
Methods: []Method{ Methods: []Method{
SetterMethod{ ContainerUpdaterMethod{
method: method{ method: method{
Comment: Comment{` Comment: Comment{`
SetSections (re)sets the list of sections to be the SetSections (re)sets the list of sections to be the
@ -1785,7 +1792,7 @@ var Main = Packages{
{Name: "sections", Type: "[]MemberSection"}, {Name: "sections", Type: "[]MemberSection"},
}, },
}, },
SetterMethod{ ContainerUpdaterMethod{
method: method{ method: method{
Comment: Comment{` Comment: Comment{`
SetMember adds or updates (or upsert) a member into SetMember adds or updates (or upsert) a member into
@ -1808,7 +1815,7 @@ var Main = Packages{
{"member", "ListMember"}, {"member", "ListMember"},
}, },
}, },
SetterMethod{ ContainerUpdaterMethod{
method: method{ method: method{
Comment: Comment{` Comment: Comment{`
RemoveMember removes a member from a section. If RemoveMember removes a member from a section. If
@ -1825,18 +1832,29 @@ var Main = Packages{
}, },
}, { }, {
Comment: Comment{` Comment: Comment{`
ListMember represents a single member in the member list. This ListMember represents a single member in the member list. Note
is a base interface that may implement more interfaces, such as that this interface should be treated as a static container:
Iconer for the user's avatar. updating a member will involve a completely new ListMember
instance with the same ID.
Note that the frontend may give everyone an avatar regardless, Note that the frontend may give everyone an avatar regardless,
or it may not show any avatars at all. or it may not show any avatars at all.
`}, `},
Name: "ListMember", Name: "ListMember",
Embeds: []EmbeddedInterface{ Embeds: []EmbeddedInterface{
{InterfaceName: "User"}, {InterfaceName: "Identifier"},
}, },
Methods: []Method{ Methods: []Method{
GetterMethod{
method: method{
Name: "Name",
Comment: Comment{`
Name returns the username or the nickname of the
member, whichever the backend should prefer.
`},
},
Returns: []NamedType{{Type: MakeQual("text", "Rich")}},
},
GetterMethod{ GetterMethod{
method: method{ method: method{
Comment: Comment{` Comment: Comment{`

View File

@ -11,7 +11,9 @@ func SolidColor(rgb uint32) uint32 {
return (rgb << 8) | 0xFF return (rgb << 8) | 0xFF
} }
// IsEmpty returns true if the given rich segment's content is empty. // IsEmpty returns true if the given rich segment's content is empty. Note that
// a rich text is not necessarily empty if the content is empty, because there
// may be images within the segments.
func (r Rich) IsEmpty() bool { func (r Rich) IsEmpty() bool {
return r.Content == "" return r.Content == "" && len(r.Segments) == 0
} }