mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-13 02:42:14 +01:00
366 lines
7.7 KiB
Go
366 lines
7.7 KiB
Go
package prinery
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xexec"
|
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
|
|
)
|
|
|
|
type command string
|
|
|
|
const (
|
|
startCommand command = "start"
|
|
restartCommand command = "restart"
|
|
stopCommand command = "stop"
|
|
logsCommand command = "logs"
|
|
jlistCommand command = "jlist"
|
|
)
|
|
|
|
const maximumRestartAttempts uint = 3
|
|
|
|
type pm2 struct {
|
|
logger xlog.Logger
|
|
config conf.Config
|
|
chromeUnit *unit
|
|
sofficeUnit *unit
|
|
processes []process
|
|
}
|
|
|
|
func NewPM2Prinery(logger xlog.Logger, config conf.Config) (Prinery, error) {
|
|
const op string = "prinery.NewPM2Prinery"
|
|
resolver := func() (*pm2, error) {
|
|
m := &pm2{
|
|
logger: logger,
|
|
config: config,
|
|
}
|
|
chromeProcesses := newChromeProcesses(config.GoogleChromeInstances())
|
|
if len(chromeProcesses) > 0 {
|
|
unit, err := newUnit(logger, processesToSpecs(chromeProcesses))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m.chromeUnit = unit
|
|
}
|
|
sofficeProcesses := newSofficesProcesses(config.SofficeInstances())
|
|
if len(sofficeProcesses) > 0 {
|
|
unit, err := newUnit(logger, processesToSpecs(sofficeProcesses))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m.sofficeUnit = unit
|
|
}
|
|
m.processes = append(chromeProcesses, sofficeProcesses...)
|
|
return m, nil
|
|
}
|
|
p, err := resolver()
|
|
if err != nil {
|
|
return nil, xerror.New(op, err)
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func (m *pm2) Start(emergency chan error) error {
|
|
const op string = "prinery.pm2.Start"
|
|
// start processes.
|
|
// those lines "works" but processes
|
|
// fail to start...
|
|
/*var wg sync.WaitGroup
|
|
result := make(chan error, len(m.processes))
|
|
for _, proc := range m.processes {
|
|
wg.Add(1)
|
|
go func(proc process, result chan error) {
|
|
result <- m.start(proc)
|
|
wg.Done()
|
|
}(proc, result)
|
|
}
|
|
wg.Wait()
|
|
close(result)
|
|
for err := range result {
|
|
if err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
}*/
|
|
for _, proc := range m.processes {
|
|
if err := m.start(proc); err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
}
|
|
// start units.
|
|
if m.chromeUnit != nil {
|
|
go m.chromeUnit.start(emergency)
|
|
}
|
|
if m.sofficeUnit != nil {
|
|
go m.sofficeUnit.start(emergency)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *pm2) HTML(
|
|
ctx context.Context,
|
|
logger xlog.Logger,
|
|
dest, fpath string,
|
|
opts ChromePrintOptions,
|
|
) error {
|
|
const op string = "prinery.pm2.HTML"
|
|
resolver := func() error {
|
|
if m.chromeUnit == nil {
|
|
return errors.New("cannot handle HTML print request as there is no chromeUnit")
|
|
}
|
|
URL := fmt.Sprintf("file://%s", fpath)
|
|
p := chromePrinter{
|
|
logger: logger,
|
|
url: URL,
|
|
opts: opts,
|
|
}
|
|
req := request{
|
|
ctx: ctx,
|
|
logger: logger,
|
|
printer: p,
|
|
dest: dest,
|
|
result: make(chan error),
|
|
}
|
|
m.chromeUnit.dispatch(req)
|
|
err := <-req.result
|
|
return err
|
|
}
|
|
if err := resolver(); err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *pm2) URL(
|
|
ctx context.Context,
|
|
logger xlog.Logger, dest, URL string,
|
|
opts ChromePrintOptions,
|
|
) error {
|
|
const op string = "prinery.pm2.URL"
|
|
resolver := func() error {
|
|
if m.chromeUnit == nil {
|
|
return errors.New("cannot handle URL print request as there is no chromeUnit")
|
|
}
|
|
p := chromePrinter{
|
|
logger: logger,
|
|
url: URL,
|
|
opts: opts,
|
|
}
|
|
req := request{
|
|
ctx: ctx,
|
|
logger: logger,
|
|
printer: p,
|
|
dest: dest,
|
|
result: make(chan error),
|
|
}
|
|
m.chromeUnit.dispatch(req)
|
|
err := <-req.result
|
|
return err
|
|
}
|
|
if err := resolver(); err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *pm2) Markdown(
|
|
ctx context.Context,
|
|
logger xlog.Logger,
|
|
dest, fpath string,
|
|
opts ChromePrintOptions,
|
|
) error {
|
|
const op string = "prinery.pm2.Markdown"
|
|
resolver := func() error {
|
|
if m.chromeUnit == nil {
|
|
return errors.New("cannot handle Markdown print request as there is no chromeUnit")
|
|
}
|
|
p, err := newMarkdownPrinter(logger, fpath, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req := request{
|
|
ctx: ctx,
|
|
logger: logger,
|
|
printer: p,
|
|
dest: dest,
|
|
result: make(chan error),
|
|
}
|
|
m.chromeUnit.dispatch(req)
|
|
err = <-req.result
|
|
return err
|
|
}
|
|
if err := resolver(); err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *pm2) Office(
|
|
ctx context.Context,
|
|
logger xlog.Logger,
|
|
dest string,
|
|
fpaths []string,
|
|
opts UnoconvPrintOptions,
|
|
) error {
|
|
const op string = "prinery.pm2.Office"
|
|
resolver := func() error {
|
|
if m.sofficeUnit == nil {
|
|
return errors.New("cannot handle Office print request as there is no sofficeUnit")
|
|
}
|
|
p := unoconvPrinter{
|
|
logger: logger,
|
|
fpaths: fpaths,
|
|
opts: opts,
|
|
}
|
|
req := request{
|
|
ctx: ctx,
|
|
logger: logger,
|
|
printer: p,
|
|
dest: dest,
|
|
result: make(chan error),
|
|
}
|
|
m.sofficeUnit.dispatch(req)
|
|
err := <-req.result
|
|
return err
|
|
}
|
|
if err := resolver(); err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *pm2) Merge(
|
|
ctx context.Context,
|
|
logger xlog.Logger,
|
|
dest string,
|
|
fpaths []string,
|
|
) error {
|
|
const op string = "prinery.pm2.Merge"
|
|
p := mergePrinter{
|
|
logger: logger,
|
|
fpaths: fpaths,
|
|
}
|
|
if err := p.print(ctx, nil, dest); err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *pm2) start(p process) error {
|
|
const op string = "prinery.pm2.start"
|
|
resolver := func() error {
|
|
// first, we try to start the process.
|
|
if err := m.run(startCommand, p); err != nil {
|
|
return err
|
|
}
|
|
// we wait the process to be ready.
|
|
m.warmup(p)
|
|
// if the process failed to start correctly,
|
|
// we have to restart it.
|
|
if !m.isViable(p) && maximumRestartAttempts > 0 {
|
|
return m.restart(p)
|
|
}
|
|
// the process is viable, let's log its
|
|
// output.
|
|
return m.logs(p)
|
|
}
|
|
if err := resolver(); err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *pm2) restart(p process) error {
|
|
const op string = "prinery.pm2.restart"
|
|
resolver := func() error {
|
|
var attempts uint
|
|
for attempts < maximumRestartAttempts {
|
|
// we restart the process.
|
|
if err := m.run(restartCommand, p); err != nil {
|
|
return err
|
|
}
|
|
// we wait the process to be ready.
|
|
m.warmup(p)
|
|
attempts++
|
|
// if the process is viable, we
|
|
// leave.
|
|
if m.isViable(p) {
|
|
return m.logs(p)
|
|
}
|
|
}
|
|
return fmt.Errorf("failed to start '%s'", p.spec().id())
|
|
}
|
|
if err := resolver(); err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *pm2) isViable(p process) bool {
|
|
if !p.viabilityFunc()(m.logger) {
|
|
return false
|
|
}
|
|
/*m.listLock.Lock()
|
|
defer m.listLock.Unlock()
|
|
return m.list.isOnline(p)*/
|
|
return true
|
|
}
|
|
|
|
func (m *pm2) warmup(p process) {
|
|
const op string = "prinery.pm2.warmup"
|
|
warmupTime := p.warmupTime()
|
|
m.logger.DebugfOp(
|
|
op,
|
|
"waiting '%v' for allowing '%s' to warmup",
|
|
warmupTime,
|
|
p.spec().id(),
|
|
)
|
|
time.Sleep(warmupTime)
|
|
}
|
|
|
|
func (m *pm2) logs(p process) error {
|
|
const op string = "prinery.pm2.logs"
|
|
if m.config.LogLevel() == xlog.DebugLevel {
|
|
if err := m.run(logsCommand, p); err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *pm2) run(pm2Cmd command, p process) error {
|
|
const op string = "prinery.pm2.run"
|
|
resolver := func() error {
|
|
args := []string{
|
|
string(pm2Cmd),
|
|
}
|
|
if pm2Cmd == startCommand {
|
|
args = append(args, p.binary())
|
|
args = append(args, fmt.Sprintf("--name=%s", p.spec().id()))
|
|
args = append(args, "--interpreter=none", "--")
|
|
args = append(args, p.args()...)
|
|
} else {
|
|
args = append(args, p.spec().id())
|
|
}
|
|
cmd, err := xexec.Command(m.logger, "pm2", args...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
xexec.LogBeforeExecute(m.logger, cmd)
|
|
return cmd.Start()
|
|
}
|
|
if err := resolver(); err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Compile-time checks to ensure type implements desired interfaces.
|
|
var (
|
|
_ = Prinery(new(pm2))
|
|
)
|