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 err := fmt.Sprintf("bug doesn't have any comments")
107 out <- core.NewImportNothing(entity.Id(lpBugID), err)
108 return
109 }
110
111 // The Launchpad API returns the bug description as the first
112 // comment, so skip it.
113 for _, lpMessage := range lpBug.Messages[1:] {
114 _, err := b.ResolveOperationWithMetadata(metaKeyLaunchpadID, lpMessage.ID)
115 if err != nil && err != cache.ErrNoMatchingOp {
116 out <- core.NewImportError(err, entity.Id(lpMessage.ID))
117 return
118 }
119
120 // If this comment already exists, we are probably
121 // updating an existing bug. We do not want to duplicate
122 // the comments, so let us just skip this one.
123 // TODO: Can Launchpad comments be edited?
124 if err == nil {
125 continue
126 }
127
128 owner, err := li.ensurePerson(repo, lpMessage.Owner)
129 if err != nil {
130 out <- core.NewImportError(err, "")
131 return
132 }
133
134 // This is a new comment, we can add it.
135 createdAt, _ := time.Parse(time.RFC3339, lpMessage.CreatedAt)
136 op, err := b.AddCommentRaw(
137 owner,
138 createdAt.Unix(),
139 lpMessage.Content,
140 nil,
141 map[string]string{
142 metaKeyLaunchpadID: lpMessage.ID,
143 })
144 if err != nil {
145 out <- core.NewImportError(err, op.Id())
146 return
147 }
148
149 out <- core.NewImportComment(op.Id())
150 }
151
152 err = b.CommitAsNeeded()
153 if err != nil {
154 out <- core.NewImportError(err, "")
155 return
156 }
157 }
158 }
159 }()
160
161 return out, nil
162}