| 1 | package main |
| 2 | |
| 3 | // DeleteTopic removes a topic and its title metadata. |
| 4 | func (a *App) DeleteTopic(topicID string) error { |
| 5 | return friendlySessionFileError(a.deleteTopic(topicID)) |
| 6 | } |
| 7 | |
| 8 | func (a *App) deleteTopic(topicID string) error { |
| 9 | a.topicTitleMutationMu.Lock() |
| 10 | defer a.topicTitleMutationMu.Unlock() |
| 11 | topicIndexMu.Lock() |
| 12 | defer topicIndexMu.Unlock() |
| 13 | return a.deleteTopicMutationLocked(topicID) |
| 14 | } |
| 15 | |
| 16 | func (a *App) deleteTopicMutationLocked(topicID string) error { |
| 17 | desktopProjectsFileMu.Lock() |
| 18 | defer desktopProjectsFileMu.Unlock() |
| 19 | return a.deleteTopicMutationProjectsLocked(topicID) |
| 20 | } |
| 21 | |
| 22 | func (a *App) deleteTopicMutationProjectsLocked(topicID string) error { |
| 23 | release, err := acquireDesktopProjectsFileLock() |
| 24 | if err != nil { |
| 25 | return err |
| 26 | } |
| 27 | defer release() |
| 28 | return a.deleteTopicMutationProjectsCrossProcessLocked(topicID) |
| 29 | } |
| 30 | |
| 31 | // deleteTopicMutationProjectsCrossProcessLocked requires the process and file |
| 32 | // locks for desktop-projects.json. |
| 33 | func (a *App) deleteTopicMutationProjectsCrossProcessLocked(topicID string) error { |
| 34 | f := loadProjectsFile() |
| 35 | indexed := map[string]bool{ |
| 36 | "": containsDesktopString(f.GlobalTopics, topicID) || containsDesktopString(f.GlobalPinnedTopics, topicID), |
| 37 | } |
| 38 | roots := make([]string, 0, len(f.Projects)+1) |
| 39 | for _, project := range f.Projects { |
| 40 | roots = append(roots, project.Root) |
| 41 | indexed[project.Root] = containsDesktopString(project.Topics, topicID) || containsDesktopString(project.PinnedTopics, topicID) |
| 42 | } |
| 43 | // The tombstone prevents stale migration and repair snapshots from |
| 44 | // resurrecting the topic while its per-scope metadata is removed. |
| 45 | if err := removeTopicFromProjectsFileCrossProcessLocked(topicID); err != nil { |
| 46 | return err |
| 47 | } |
| 48 | roots = append(roots, "") |
| 49 | for _, root := range roots { |
| 50 | titles, err := loadTopicTitlesForUpdate(root) |
| 51 | if err != nil { |
| 52 | if indexed[root] { |
| 53 | return err |
| 54 | } |
| 55 | continue |
| 56 | } |
| 57 | if _, hasTitle := titles[topicID]; !hasTitle && !indexed[root] { |
| 58 | continue |
| 59 | } |
| 60 | if err := deleteTopicState(root, topicID); err != nil { |
| 61 | return err |
| 62 | } |
| 63 | } |
| 64 | a.emitProjectTreeMetadataChanged() |
| 65 | return nil |
| 66 | } |
| 67 |