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