package main import ( "flag" "fmt" "log" "os" sqliterepo "git.vakhrushev.me/av/transcriber/internal/adapter/repo/sqlite" "git.vakhrushev.me/av/transcriber/internal/config" "git.vakhrushev.me/av/transcriber/internal/entity" "git.vakhrushev.me/av/transcriber/internal/ident" ) // stepResume — чем возврат в работу подписывается в журнале событий записи. const stepResume = "resume" // runResume возвращает остановленную запись в работу. // // Подкоманда встала на место панели владельца: панели у сервиса больше нет, а // экраны правки записи приносят отдельные задачи. Из всего, что владелец делал // панелью, отложить до экранов нельзя было одно — возврат остановленной записи. // // **Колонок подкоманда не пишет.** Перечень полей, которые возврат обязан // сбросить — признак остановки, признак захвата и срок его протухания, число // отказов, паузу и время входа в рубеж, — исполняет домен одним действием. // Рука, забывшая любое из них, оставила бы запись либо невидимой для захвата, // либо останавливаемой снова первым же захватом — молча, без единой строки. // // Событие журнала записи пишется с происхождением «человек»: иначе запись, // побывавшая остановленной и вернувшаяся в работу, неотличима в журнале от // записи, которую конвейер вёл без остановок, а происхождение события перестаёт // различать что-либо. func runResume(args []string) { flags := flag.NewFlagSet("resume", flag.ExitOnError) configPath := flags.String("c", "config.toml", "путь к файлу настроек") if err := flags.Parse(args); err != nil { os.Exit(2) } if flags.NArg() != 1 { fmt.Fprint(os.Stderr, "укажи идентификатор записи: devtools resume [-c config.toml] \n") os.Exit(2) } recordID, ok := ident.Parse(flags.Arg(0)) if !ok { log.Fatalf("идентификатор записи не читается: %q", flags.Arg(0)) } cfg, err := config.LoadConfig(*configPath) if err != nil { log.Fatalf("настройки не читаются: %v", err) } if err := cfg.Storage.Validate(); err != nil { log.Fatalf("настройки хранилища негодны: %v", err) } db, err := sqliterepo.Open(cfg.Storage.DataDir, sqliterepo.Settings{ BusyTimeoutMs: cfg.Storage.BusyTimeoutMs, ReadConnections: cfg.Storage.ReadConnections, }) if err != nil { log.Fatalf("база не открывается: %v", err) } defer func() { if err := db.Close(); err != nil { log.Printf("база закрылась с отказом: %v", err) } }() records := sqliterepo.NewAudioRecordRepository(db) events := sqliterepo.NewRecordEventRepository(db) record, err := records.Get(recordID) if err != nil { log.Fatalf("запись не читается: %v", err) } if !record.IsHalted() { log.Fatalf("запись %s не остановлена: возвращать в работу нечего", recordID) } // Захват снимает сам домен, поэтому сохранение идёт **безусловным**: держателя // у остановленной записи нет, и сверять признак захвата не с чем. record.Resume() if err := records.Save(record, ""); err != nil { log.Fatalf("запись не сохраняется: %v", err) } if err := events.Append(&entity.RecordEvent{ RecordID: recordID, Origin: entity.EventOriginHuman, Step: stepResume, Outcome: entity.EventOutcomeResumed, }); err != nil { log.Fatalf("событие журнала записи не сохраняется: %v", err) } log.Printf("запись %s возвращена в работу с рубежа %s", recordID, record.State) }