mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-16 07:06:45 +00:00
Add a dedicated go-routine whose sole responsibility is to manage the life time of the IMAP and SMTP servers and their listeners. The current implementation behaves the same way as the previous state. The new behavior will be implemented in a follow MR.
160 lines
3.4 KiB
Go
160 lines
3.4 KiB
Go
// Copyright (c) 2023 Proton AG
|
|
//
|
|
// This file is part of Proton Mail Bridge.
|
|
//
|
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Proton Mail Bridge 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 General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package cpc
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
var ErrRequestHasNoReply = errors.New("request has no reply channel")
|
|
var ErrExpectedReply = errors.New("request does not have reply channel")
|
|
|
|
// Utilities to implement Chanel Procedure Calls. Similar in concept to RPC, but with between go-routines.
|
|
|
|
type RequestReply struct {
|
|
Value any
|
|
Error error
|
|
}
|
|
|
|
type Request struct {
|
|
Value any
|
|
Reply chan RequestReply
|
|
}
|
|
|
|
func NewRequest(value any) *Request {
|
|
return &Request{
|
|
Value: value,
|
|
Reply: make(chan RequestReply),
|
|
}
|
|
}
|
|
|
|
func NewRequestWithoutReply(value any) *Request {
|
|
return &Request{
|
|
Value: value,
|
|
Reply: nil,
|
|
}
|
|
}
|
|
|
|
func (r *Request) Close() {
|
|
if r.Reply != nil {
|
|
panic("request reply has not been sent")
|
|
}
|
|
}
|
|
|
|
func (r *Request) SendReply(ctx context.Context, value any, err error) {
|
|
if r.Reply == nil {
|
|
panic("request has no reply")
|
|
}
|
|
|
|
defer func() {
|
|
close(r.Reply)
|
|
r.Reply = nil
|
|
}()
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
case r.Reply <- RequestReply{
|
|
Value: value,
|
|
Error: err,
|
|
}:
|
|
}
|
|
}
|
|
|
|
type CPC struct {
|
|
request chan *Request
|
|
}
|
|
|
|
func NewCPC() *CPC {
|
|
return &CPC{
|
|
request: make(chan *Request),
|
|
}
|
|
}
|
|
|
|
// Receive is meant to be called by the code that is supposed to handle the requests that arrive.
|
|
func (c *CPC) Receive(ctx context.Context, f func(context.Context, *Request)) {
|
|
for request := range c.request {
|
|
f(ctx, request)
|
|
request.Close()
|
|
}
|
|
}
|
|
|
|
func (c *CPC) ReceiveCh() <-chan *Request {
|
|
return c.request
|
|
}
|
|
|
|
func (c *CPC) Close() {
|
|
close(c.request)
|
|
}
|
|
|
|
// SendNoReply sends a request which doesn't expect a reply.
|
|
func (c *CPC) SendNoReply(ctx context.Context, value any) error {
|
|
return c.executeNoReplyImpl(ctx, NewRequestWithoutReply(value))
|
|
}
|
|
|
|
// SendWithReply sends a request which expects a reply.
|
|
func (c *CPC) SendWithReply(ctx context.Context, value any) (any, error) {
|
|
return c.executeReplyImpl(ctx, NewRequest(value))
|
|
}
|
|
|
|
func SendWithReplyType[T any](ctx context.Context, c *CPC, value any) (T, error) {
|
|
val, err := c.executeReplyImpl(ctx, NewRequest(value))
|
|
if err != nil {
|
|
var t T
|
|
return t, err
|
|
}
|
|
|
|
switch vt := val.(type) {
|
|
case T:
|
|
return vt, nil
|
|
default:
|
|
var t T
|
|
return t, fmt.Errorf("reply type does not match")
|
|
}
|
|
}
|
|
|
|
func (c *CPC) executeNoReplyImpl(ctx context.Context, request *Request) error {
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case c.request <- request:
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *CPC) executeReplyImpl(ctx context.Context, request *Request) (any, error) {
|
|
if request.Reply == nil {
|
|
return nil, ErrExpectedReply
|
|
}
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
case c.request <- request:
|
|
}
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
case reply := <-request.Reply:
|
|
return reply.Value, reply.Error
|
|
}
|
|
}
|