- адреса приложения переехали в своё пространство `/app/`, опрос готовности убран целиком: рубеж и причину остановки владелец узнаёт карточкой записи, текст — отдельным адресом названного вида - заведена единая точка отображения доменной ошибки и слой, приводящий к той же форме отказы библиотеки: тело несёт машиночитаемый код рядом с сообщением - у записи появились имя файла отправителя, длительность и размер своими колонками, а у ленты владельца — свой индекс: без него страница сканировала весь архив сервиса
151 lines
6.8 KiB
Go
151 lines
6.8 KiB
Go
package entity
|
|
|
|
import "time"
|
|
|
|
// Work — чью работу ждёт запись, стоя на рубеже. От этого зависит предел
|
|
// простоя: своя работа мерится одним числом, ожидание чужой операции — другим.
|
|
// Граница проходит по исполнителю, а не по рубежу: число на каждый рубеж
|
|
// назвало бы разными вещи, различающиеся только им.
|
|
type Work int
|
|
|
|
const (
|
|
// WorkOwn — работу делаем мы сами.
|
|
WorkOwn Work = iota
|
|
// WorkForeign — ждём операцию внешнего сервиса.
|
|
WorkForeign
|
|
)
|
|
|
|
// Stage — объявление рубежа одним местом.
|
|
//
|
|
// Из этого перечня выводятся все потребители: выбор следующего шага, отбор
|
|
// захвата, срок протухания захвата и предел простоя. Перечислять рубежи порознь
|
|
// в каждом потребителе нельзя: рубеж, забытый в отборе захвата, не выдаётся ни
|
|
// одному воркеру никогда, а пустой прогон по инварианту проекта не пишется в
|
|
// журнал и не считается в метрику — запись встала бы без единого следа.
|
|
type Stage struct {
|
|
Name string
|
|
// Work — чью работу ждём, стоя на этом рубеже.
|
|
Work Work
|
|
// AcquireTimeout — срок протухания захвата. Едет с рубежом, а не с воркером:
|
|
// воркер не привязан к шагу и не знает заранее, что вытянет.
|
|
AcquireTimeout time.Duration
|
|
// Terminal — рубеж, из которого запись в работу не берут. Такой рубеж не
|
|
// подпадает и под предел простоя: стоять в нём запись будет вечно по
|
|
// построению.
|
|
Terminal bool
|
|
}
|
|
|
|
// Сроки захвата. Каждый не меньше того, что его шаг может занять на самом
|
|
// длинном допустимом входе: расчётный потолок записи — шесть часов, и приведение
|
|
// такой записи идёт дольше часа по построению.
|
|
const (
|
|
normalizeAcquireTimeout = 8 * time.Hour
|
|
submitAcquireTimeout = 8 * time.Hour
|
|
pollAcquireTimeout = time.Hour
|
|
finishAcquireTimeout = time.Hour
|
|
)
|
|
|
|
// stages — цепочка рубежей в порядке прохождения.
|
|
var stages = []Stage{
|
|
{Name: StateUploaded, Work: WorkOwn, AcquireTimeout: normalizeAcquireTimeout},
|
|
{Name: StateNormalized, Work: WorkOwn, AcquireTimeout: submitAcquireTimeout},
|
|
{Name: StateSubmitted, Work: WorkForeign, AcquireTimeout: pollAcquireTimeout},
|
|
{Name: StateTranscribed, Work: WorkOwn, AcquireTimeout: finishAcquireTimeout},
|
|
{Name: StateDone, Terminal: true},
|
|
}
|
|
|
|
// WorkingStages — рубежи, с которых запись берут в работу.
|
|
func WorkingStages() []Stage {
|
|
out := make([]Stage, 0, len(stages))
|
|
for _, s := range stages {
|
|
if !s.Terminal {
|
|
out = append(out, s)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// StageByName находит рубеж по имени. Второе значение ложно у рубежа, которого
|
|
// в цепочке нет: запись с таким рубежом до шага не доходит.
|
|
func StageByName(name string) (Stage, bool) {
|
|
for _, s := range stages {
|
|
if s.Name == name {
|
|
return s, true
|
|
}
|
|
}
|
|
return Stage{}, false
|
|
}
|
|
|
|
// ListFilter — состояние записи, по которому её отбирает список приложения.
|
|
//
|
|
// Состояний три, а не два, и это не педантизм. Остановленная запись не в работе
|
|
// и не завершена: при отборе надвое она выпала бы из обеих половин — исчезла бы
|
|
// из списка при любом значении отбора, — хотя ради неё человек список и
|
|
// открывает.
|
|
type ListFilter string
|
|
|
|
const (
|
|
// ListFilterWorking — запись идёт по конвейеру.
|
|
ListFilterWorking ListFilter = "working"
|
|
// ListFilterHalted — запись остановлена признаком.
|
|
ListFilterHalted ListFilter = "halted"
|
|
// ListFilterDone — запись прошла конвейер.
|
|
ListFilterDone ListFilter = "done"
|
|
)
|
|
|
|
// ParseListFilter узнаёт состояние отбора по его имени. Второе значение ложно у
|
|
// имени, которого в перечне нет: такой отбор — негодный ввод, а не пустая
|
|
// выборка.
|
|
func ParseListFilter(v string) (ListFilter, bool) {
|
|
switch ListFilter(v) {
|
|
case ListFilterWorking, ListFilterHalted, ListFilterDone:
|
|
return ListFilter(v), true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// TerminalStages — рубежи, из которых запись в работу не берут.
|
|
//
|
|
// Выводится из дескриптора наравне с WorkingStages: отбор списка — очередной
|
|
// потребитель словаря рубежей, и перечислять их у него строкой запроса нельзя.
|
|
// Рубеж, добавленный конвейером, иначе молча поменял бы состав всех трёх
|
|
// состояний отбора.
|
|
func TerminalStages() []Stage {
|
|
out := make([]Stage, 0, len(stages))
|
|
for _, s := range stages {
|
|
if s.Terminal {
|
|
out = append(out, s)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// StageNames разворачивает рубежи в их имена — для запроса к хранилищу.
|
|
func StageNames(list []Stage) []string {
|
|
out := make([]string, 0, len(list))
|
|
for _, s := range list {
|
|
out = append(out, s.Name)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// StuckLimits — пределы простоя, приходящие из настроек.
|
|
type StuckLimits struct {
|
|
// Own — предел на своей работе.
|
|
Own time.Duration
|
|
// Foreign — предел на ожидании чужой операции.
|
|
Foreign time.Duration
|
|
}
|
|
|
|
// Limit — предел простоя для этого рубежа. У конечного рубежа предела нет:
|
|
// запись стоит в нём вечно по построению.
|
|
func (s Stage) Limit(limits StuckLimits) (time.Duration, bool) {
|
|
if s.Terminal {
|
|
return 0, false
|
|
}
|
|
if s.Work == WorkForeign {
|
|
return limits.Foreign, true
|
|
}
|
|
return limits.Own, true
|
|
}
|