1package launchpad
2
3import (
4 "context"
5 "fmt"
6 "time"
7
8 "github.com/MichaelMure/git-bug/bridge/core"
9 "github.com/MichaelMure/git-bug/bug"
10 "github.com/MichaelMure/git-bug/cache"
11 "github.com/MichaelMure/git-bug/entity"
12)
13
14type launchpadImporter struct {
15 conf core.Configuration
16}
17
18func (li *launchpadImporter) Init(conf core.Configuration) error {
19 li.conf = conf
20 return nil
21}
22
23const (
24 metaKeyLaunchpadID = "launchpad-id"
25 metaKeyLaunchpadLogin = "launchpad-login"
26)
27
28func (li *launchpadImporter) ensurePerson(repo *cache.RepoCache, owner LPPerson) (*cache.IdentityCache, error) {
29 // Look first in the cache
30 i, err := repo.ResolveIdentityImmutableMetadata(metaKeyLaunchpadLogin, owner.Login)
31 if err == nil {
32 return i, nil
33 }
34 if _, ok := err.(entity.ErrMultipleMatch); ok {
35 return nil, err
36 }
37
38 return repo.NewIdentityRaw(
39 owner.Name,
40 "",
41 owner.Login,
42 "",
43 map[string]string{
44 metaKeyLaunchpadLogin: owner.Login,
45 },
46 )
47}
48
49func (li *launchpadImporter) ImportAll(ctx context.Context, repo *cache.RepoCache, since time.Time) (<-chan core.ImportResult, error) {
50 out := make(chan core.ImportResult)
51 lpAPI := new(launchpadAPI)
52
53 err := lpAPI.Init()
54 if err != nil {
55 return nil, err
56 }
57
58 lpBugs, err := lpAPI.SearchTasks(ctx, li.conf["project"])
59 if err != nil {
60 return nil, err
61 }
62
63 go func() {
64 for _, lpBug := range lpBugs {
65 select {
66 case <-ctx.Done():
67 return
68 default:
69 lpBugID := fmt.Sprintf("%d", lpBug.ID)
70 b, err := repo.ResolveBugCreateMetadata(metaKeyLaunchpadID, lpBugID)
71 if err != nil && err != bug.ErrBugNotExist {
72 out <- core.NewImportError(err, entity.Id(lpBugID))
73 return
74 }
75
76 owner, err := li.ensurePerson(repo, lpBug.Owner)
77 if err != nil {
78 out <- core.NewImportError(err, entity.Id(lpBugID))
79 return
80 }
81
82 if err == bug.ErrBugNotExist {
83 createdAt, _ := time.Parse(time.RFC3339, lpBug.CreatedAt)
84 b, _, err = repo.NewBugRaw(
85 owner,
86 createdAt.Unix(),
87 lpBug.Title,
88 lpBug.Description,
89 nil,
90 map[string]string{
91 core.MetaKeyOrigin: target,
92 metaKeyLaunchpadID: lpBugID,
93 },
94 )
95 if err != nil {
96 out <- core.NewImportError(err, entity.Id(lpBugID))
97 return
98 }
99
100 out <- core.NewImportBug(b.Id())
101
102 }
103
104 /* Handle messages */
105 if len(lpBug.Messages) == 0 {
106 return
107 }
108
109 // The Launchpad API returns the bug description as the first
110 // comment, so skip it.
111 for _, lpMessage := range lpBug.Messages[1:] {
112 _, err := b.ResolveOperationWithMetadata(metaKeyLaunchpadID, lpMessage.ID)
113 if err != nil && err != cache.ErrNoMatchingOp {
114 out <- core.NewImportError(err, entity.Id(lpMessage.ID))
115 return
116 }
117
118 // If this comment already exists, we are probably
119 // updating an existing bug. We do not want to duplicate
120 // the comments, so let us just skip this one.
121 // TODO: Can Launchpad comments be edited?
122 if err == nil {
123 continue
124 }
125
126 owner, err := li.ensurePerson(repo, lpMessage.Owner)
127 if err != nil {
128 out <- core.NewImportError(err, "")
129 return
130 }
131
132 // This is a new comment, we can add it.
133 createdAt, _ := time.Parse(time.RFC3339, lpMessage.CreatedAt)
134 op, err := b.AddCommentRaw(
135 owner,
136 createdAt.Unix(),
137 lpMessage.Content,
138 nil,
139 map[string]string{
140 metaKeyLaunchpadID: lpMessage.ID,
141 })
142 if err != nil {
143 out <- core.NewImportError(err, op.Id())
144 return
145 }
146
147 out <- core.NewImportComment(op.Id())
148 }
149
150 if !b.NeedCommit() {
151 out <- core.NewImportNothing(b.Id(), "no imported operation")
152 } else if err := b.Commit(); err != nil {
153 out <- core.NewImportError(err, "")
154 return
155 }
156 }
157 }
158 }()
159
160 return out, nil
161}