Update changes an instruction's name/description/content, keeps the managed file in sync, and re-targets symlink installs when the safe-name changes.
(ctx context.Context, id int64, name, desc, content string)
| 298 | // Update changes an instruction's name/description/content, keeps the managed |
| 299 | // file in sync, and re-targets symlink installs when the safe-name changes. |
| 300 | func (s *Store) Update(ctx context.Context, id int64, name, desc, content string) (Instruction, error) { |
| 301 | if err := validateName(name); err != nil { |
| 302 | return Instruction{}, err |
| 303 | } |
| 304 | if err := s.Init(ctx); err != nil { |
| 305 | return Instruction{}, err |
| 306 | } |
| 307 | db, err := s.open() |
| 308 | if err != nil { |
| 309 | return Instruction{}, err |
| 310 | } |
| 311 | defer db.Close() |
| 312 | |
| 313 | current, err := getTx(ctx, db, id) |
| 314 | if err != nil { |
| 315 | return Instruction{}, err |
| 316 | } |
| 317 | |
| 318 | exists, err := nameExists(ctx, db, name, id) |
| 319 | if err != nil { |
| 320 | return Instruction{}, fmt.Errorf("instructions: check name: %w", err) |
| 321 | } |
| 322 | if exists { |
| 323 | return Instruction{}, fmt.Errorf("%w: %q", ErrDuplicateName, name) |
| 324 | } |
| 325 | |
| 326 | oldFile := managedFilePath(current.Name) |
| 327 | newFile := managedFilePath(name) |
| 328 | |
| 329 | if err := writeManagedFile(name, content); err != nil { |
| 330 | return Instruction{}, err |
| 331 | } |
| 332 | |
| 333 | now := time.Now().UTC().Format(rfc) |
| 334 | if _, err := db.ExecContext(ctx, `UPDATE instructions SET name = ?, description = ?, content = ?, updated_at = ? WHERE id = ?`, name, desc, content, now, id); err != nil { |
| 335 | // Best-effort rollback of the managed file content. |
| 336 | _ = writeManagedFile(current.Name, current.Content) |
| 337 | if strings.Contains(err.Error(), "UNIQUE") { |
| 338 | return Instruction{}, fmt.Errorf("%w: %q", ErrDuplicateName, name) |
| 339 | } |
| 340 | return Instruction{}, fmt.Errorf("instructions: update: %w", err) |
| 341 | } |
| 342 | |
| 343 | if oldFile != newFile { |
| 344 | // Re-target every symlink install that pointed at the old managed file. |
| 345 | installs, err := installsFor(ctx, db, id) |
| 346 | if err != nil { |
| 347 | return Instruction{}, err |
| 348 | } |
| 349 | for _, ins := range installs { |
| 350 | if ins.LinkKind != "symlink" { |
| 351 | continue // copy installs go stale until re-install |
| 352 | } |
| 353 | retargetSymlink(ins.TargetPath, newFile) |
| 354 | } |
| 355 | os.Remove(oldFile) |
| 356 | } |
| 357 |